Inspiration
Getting dressed for an important occasion is stressful. You have a mental image of how you want to look, but you can't actually see yourself in that outfit or know how your skin will cooperate by the day of the event. Professional styling and skincare advice are expensive and inaccessible. We wanted to democratize that experience — give anyone a personal stylist and dermatologist in their pocket, driven by AI, that adapts to the specific occasion they're dressing for.
The "meet the moment" philosophy drove every decision: it's not about looking perfect, it's about showing up as the best version of yourself for that specific event. A job interview demands different styling than a wedding, and your skin's concerns on a given day should inform both skincare prep and outfit color choices.
We were also inspired by the rapid advancement of AI vision APIs — particularly YouCam's suite of skin analysis, simulation, and virtual try-on tools — which made it possible to build something that would have required a team of ML engineers just a few years ago.
What it does
Mirror is an occasion-aware self-presentation app. You type what you're dressing for ("job interview Friday", "wedding next Saturday"), upload a selfie and a full-body photo, and Mirror returns:
Skin Report
- 6 concern scores — redness, acne, texture, oiliness, dark circles, hydration — each scored 0-100 with color-coded progress bars (green/amber/red)
- Actionable tips per concern — e.g., "A calming, fragrance-free mask the night before can bring redness down fast" or "A gentle exfoliant 48 hours out, not the night before, evens texture without irritation"
- Mask overlays — YouCam returns region-specific masks showing exactly where each concern appears on the face
Before/After Simulation
- Each concern row has a "See the difference" toggle
- Lazily triggers a YouCam skin-simulation task (max intensity for that concern only)
- Shows the original selfie vs. the simulated "after" image side-by-side
- Results are cached — toggling back and forth is instant after the first load
Outfit Try-On (Two Modes)
Catalog Mode — Real garment photos (blazers, trousers, shirts, etc.) from a seed catalog, rendered on the user's body via YouCam cloth-v4 VTO. The stylist agent picks up to 3 garments based on occasion formality and skin concerns.
Fabric Mode — AI-generated fabric styles (silks, prints, textures) from YouCam's predefined template library (~30-60 styles), rendered on the user's body via YouCam fabric VTO. Auto-generates when the user switches to the "Fabric" tab.
Both modes show a horizontal carousel of outfit cards with the rendered image, garment/template name, and the stylist's reasoning for each pick.
Styling Narrative
- After outfits are ready, the user can generate a 2-3 sentence narrative
- Written in second person ("you"), warm and specific
- References actual skin concerns and garment choices with their reasoning
- Persisted on the look for future reference
Look History
- Every look is saved and browsable from the dashboard
- Each look shows occasion text, creation date, skin task status, outfit status, and concern scores
- Click through to view full skin report, outfit carousel, or regenerate narrative
How we built it
Frontend
- Next.js 16 (App Router, Turbopack) with TypeScript
- Tailwind CSS 4 + shadcn/ui for component primitives (Card, Progress, Badge, Button, Skeleton, Sidebar, Breadcrumb)
- Remix Icon for iconography
- Client components for interactive flows (polling, toggles, carousels)
- Server components for data fetching where possible
Backend
- Next.js API routes handle the full pipeline:
POST /api/looks— occasion parsing + look creationPOST /api/skin-analysis— selfie upload + task creationGET /api/skin-analysis/[taskId]— polling + result persistencePOST /api/cloth-vto— body upload + stylist agent + garment VTO tasksGET /api/cloth-vto/[taskId]— polling + render persistencePOST /api/fabric-vto— stylist agent + fabric VTO tasks (reuses stored body file ID)GET /api/fabric-vto/[taskId]— polling + render persistencePOST /api/skin-simulation— per-concern simulation task creationGET /api/skin-simulation/[taskId]— polling + simulation persistenceGET /api/looks/[lookId]— look detail aggregationGET/PATCH /api/looks/[lookId]/outfit— catalog renders + selectionGET /api/looks/[lookId]/fabric— fabric renders + statusGET /api/looks/[lookId]/skin— skin scan results
AI Agents (LangChain / LangGraph)
- Planner agent (
lib/agents/planner.ts) — parses raw occasion text into structured data:occasionType(interview, wedding, date, etc.),formality(casual, smart_casual, business, formal), andtimeframeDays(days until the occasion) - Stylist agent (
lib/agents/stylist-agent.ts) — an LLM call (OpenAI) that receives the parsed occasion, a catalog (garments or fabric templates with id/name/category/color), and skin concern scores. Returns up to 3 selections with garment IDs and human-readable reasons. Uses Zod schema validation and filters out hallucinated IDs. - Synthesis agent (
lib/agents/synthesis-agent.ts) — a LangGraph StateGraph with a single node that generates a 2-3 sentence narrative combining occasion, skin findings, and outfit choices. - Orchestration (
lib/agents/graph.ts) — LangGraph StateGraph wrapping the stylist agent, designed to be extended with additional nodes (skin-agent, etc.)
AI Vision (YouCam / Perfect Corp)
- Skin Analysis (
/s2s/v2.1/task/skin-analysis) — 6 concerns with mask overlays, JSON output - Skin Simulation (
/s2s/v2.0/task/skin-simulation) — per-concern before/after at 0.0-1.0 intensity - Cloth VTO (
/s2s/v2.0/task/cloth-v4) — real garments on body - Fabric VTO (
/s2s/v2.0/task/fabric) — AI fabric styles on body - Template listing (
/s2s/v2.0/task/template/fabric) — paginated fabric template catalog - File upload (
/s2s/v2.0/file) — two-step presigned URL upload
Database
- PostgreSQL via Neon, managed with Drizzle ORM
- 7 tables:
looks,tasks,skinScans,outfitRenders,garments,fabricTemplates,skinSimulations - All async task state tracked in
taskstable for audit and polling - Schema pushed via
drizzle-kit push
Storage
- Vercel Blob (private access) for all persisted images
persistYouCamResult()downloads from YouCam's 2-hour URL and re-uploads to blobsignedBlobUrl()generates short-lived signed URLs for client-side rendering- All image persistence happens immediately upon task completion
Authentication
- Clerk for user auth (sign-in, session management)
- All API routes check
auth()and filter byuserId
Challenges we ran into
1. YouCam API Async Lifecycle
Every YouCam task follows the same pattern: create → poll → result. Results expire after 2 hours. We had to build:
- A polling system with 2.5s intervals and 60s timeout
- Error parsing that handles both short codes (
error_lighting_dark) and human-readable sentences - Immediate persistence of results to Vercel Blob before expiry
- Database tracking of task state for audit and retry
The skin-analysis poll route alone is ~180 lines of error handling and persistence logic.
2. Skin Simulation src Reuse
The skin simulation API needs the original selfie as input. Initially we re-uploaded bytes for each simulation, wasting quota and adding latency. We solved this by:
- Persisting the
src_file_idfrom the analysis upload on theskinScansrow - Storing the selfie as a blob URL during the POST (not during polling)
- Having the simulation POST route read
srcFileIdfrom the existing scan row - Creating a placeholder
skinScansrow during POST so the data is available immediately, then updating it when polling completes
3. Catalog vs. Fabric Render Separation
Both catalog garments and fabric templates write to the same outfitRenders table. Without filtering, the fabric tab showed catalog renders (with "upper_body" as the name). We fixed this by:
- Adding a nullable
fabricTemplateIdFK tooutfitRenders - Filtering catalog route by
garmentId IS NOT NULL - Filtering fabric route by
fabricTemplateId IS NOT NULL - Filtering the look detail summary by
garmentId IS NOT NULL
4. Stylist Agent Hallucination
The LLM occasionally invented garment IDs that didn't exist in the catalog. We added:
- Zod schema validation on the structured output
- A defensive filter that drops any selection whose ID doesn't match a real catalog entry
- Prompt instructions emphasizing "only use garment ids that appear in the catalog"
5. Before/After Toggle UX
Getting per-concern toggles to:
- Lazily trigger simulation (avoiding 6 concurrent API calls on page load)
- Show loading state with a spinner
- Cache results for instant toggling
- Handle errors gracefully
...required careful state management in a client component with useState and useRef for polling intervals.
6. Fabric Tab Auto-Generation
The fabric renders don't exist until the user switches to the "Fabric" tab. This created a chicken-and-egg problem: the toggle only showed when both modes had renders, but fabric renders couldn't be generated without the toggle. We solved this by:
- Always showing the toggle when catalog renders exist
- Auto-triggering fabric generation on first switch to fabric mode
- Polling fabric tasks every 3s while running
- Showing "Preparing your outfits…" during generation
7. Next.js App Router Nuances
- Route groups
(dashboard)for shared layout without URL segments - Dynamic segments
[lookId]with async params (Next.js 16 requirement) - Server/client component boundaries for data fetching
- Middleware-free auth via Clerk's
auth()in API routes
8. YouCam API Documentation Gaps
The docs pages render client-side (JavaScript), making them hard to scrape. We had to:
- Fetch the OpenAPI YAML bundles directly (
/_bundle/reference/ai_fabric.yaml) - Reverse-engineer exact request/response shapes from the specs
- Test against the live API to confirm field names and nesting
Accomplishments that we're proud of
End-to-End AI Pipeline
From raw text input ("job interview Friday") to skin analysis to outfit rendering to narrative generation — all orchestrated through real API calls to YouCam and OpenAI. No mock data, no placeholder images.
Hybrid Outfit System
Both real catalog garments and AI-generated fabric styles, switchable in a single carousel view. The user can compare "real blazer" vs. "AI silk print" on their own body.
Per-Concern Before/After Simulation
Each skin concern gets its own toggle that lazily generates a simulation showing what the user's skin would look like with that concern reduced. This makes the abstract concept of "improving your skin" concrete and visual.
Occasion-Aware Styling
The stylist agent doesn't pick random clothes — it reasons about:
- Formality level (casual vs. business vs. formal)
- Occasion type (interview, wedding, date, etc.)
- Skin concerns (e.g., recommending colors that complement redness)
- Timeframe (aggressive skin prep for 3-day vs. 14-day timelines)
Resilient Infrastructure
- Every YouCam result persisted to blob before 2-hour expiry
- Task state tracked in database for audit, retry, and UI polling
- Errors parsed into user-friendly messages with actionable guidance
- Placeholder rows inserted during POST so data is available immediately
Clean Architecture
- Separation of concerns: YouCam client, upload, poll, task-specific modules
- Reusable polling pattern across 4 task types
- Agent pipeline designed for extensibility (LangGraph StateGraph)
- Type-safe throughout (TypeScript + Zod + Drizzle)
What we learned
Async API Orchestration is Hard
Polling, persistence, error handling, and state management across multiple concurrent tasks requires careful architecture. The "persist before expiry" constraint added urgency to every code path. We learned to:
- Insert placeholder DB rows during POST (don't wait for task completion)
- Poll aggressively (2.5s) but with reasonable timeouts (60s)
- Handle network glitches gracefully (keep polling on fetch errors)
- Parse errors defensively (YouCam returns both codes and sentences)
LLM Agents Need Guardrails
Trusting model output blindly leads to broken UX. We learned to:
- Use Zod schema validation on structured output
- Filter hallucinated IDs against a known set
- Include explicit constraints in system prompts ("only use garment ids that appear in the catalog")
- Keep temperature low (0.3) for deterministic selection
Database Design Matters Early
Adding columns after the fact (srcFileId, storedSelfieUrl, bodyFileId, fabricTemplateId) required migrations and careful backfilling. We learned to:
- Design for extensibility upfront (nullable FKs, flexible JSONB columns)
- Use placeholder rows to avoid race conditions between POST and poll
- Filter queries by type when multiple entity types share a table
YouCam's API Surface is Rich but Undocumented
The docs render client-side, making them hard to scrape. We learned to:
- Fetch OpenAPI YAML bundles directly
- Test against the live API to confirm field names
- Build typed wrappers that match the actual response shapes
- Handle both
task_statusanderrorfields (task can be "success" with an error)
Hybrid Approaches Beat Either/Or
Keeping both catalog and fabric VTO gave us the best of both worlds:
- Catalog: real garments for realism and recognizability
- Fabric: AI styles for creative exploration and variety
- User can switch between modes and compare
Next.js App Router Has Nuances
- Route groups for layout sharing
- Async params in Next.js 16 (must await
params) - Server vs. client component boundaries
useEffectdependency arrays and stale closures in polling
What's next for Mirror
Near-term
- Skin plan over time — track skin concerns across multiple looks to show progress ("your redness score dropped from 65 to 42 over 3 weeks")
- Auto-polling on dashboard — refresh look status without manual page reload
- Error retry — allow users to retry failed tasks without re-uploading photos
- Outfit comparison view — side-by-side catalog vs. fabric on the same body
Medium-term
- Wardrobe integration — let users upload their own clothes instead of just the seed catalog
- Social sharing — share a look (outfit + narrative) with friends for feedback
- Calendar integration — auto-detect upcoming occasions from Google Calendar and pre-generate looks
- Multi-outfit comparison — A/B test different outfit options
Long-term
- Mobile app — native capture with the YouCam Camera Kit for better photo quality
- Personalized recommendations — learn from user selections to improve future stylist agent picks
- Accessibility — voice input for occasion text, screen reader support for skin reports
- Multi-language support — occasion parsing and narratives in multiple languages
- AR try-on — real-time fabric overlay using device camera
Built with Next.js, YouCam API, LangChain, Drizzle ORM, Clerk, and Vercel Blob.
Built With
- neon-postgresql
- nextjs
- tailwindcss
- typescript
- vercel-blob
- youcam-api

Log in or sign up for Devpost to join the conversation.