Alexei Rojas Quiroga
← All projects

Personal project

Mirra — AI Wardrobe & Outfit Planner

Photograph your clothes; get outfits built from what you already own

Status
In progress
Role
Solo: product, architecture, mobile app, Supabase backend and edge functions, CI

Architecture

  • Hexagonal core, framework-free
  • Ownership enforced by RLS + composite FKs
  • Photos via presigned R2 URLs
  • Vision model called server-side

The adapter gets a presigned URL, uploads the photo straight to R2, asks the classifier (which reads the photo and calls the vision model), the use case validates the proposal in the domain and the garment is saved under row-level security.

Architecture · 10 nodes · 2 flows
WHAT I BUILTExpo / React N…Mobile appCapture, wardrobe grid,detailSupabase AuthSign-inEmail OTP, session inAsyncStorageTypeScriptApplication usecasesaddGarmentFromPhoto,listWardrobeTypeScriptDomain modelGarment, Outfit,isWearableForTypeScriptInfrastructureadaptersRepos, R2 storage,classifier, URL cachePostgres + RLSWardrobe databaseGarments, outfits,pgvectorEdge Function …Garment classifierReads photo, asksvision modelOpenAI-compati…Vision modelDescribes garment fromphotoCloudflare R2Photo storagePrivate bucket ofgarment photosEdge Function …Upload URL functioncreate-upload-url1invoke use case23456789
  • Service / compute
  • Data store
  • AI
  • Client
  • External system
  • Synchronous
  • Async / loop
Scroll sideways to see the full diagram

How it flows, step by step

Click a step to jump to it. Click a component for details.

What it does

Mirra is a native iOS/Android app (Expo / React Native) that turns a user's own clothes into a digital closet. A garment photo is uploaded to private object storage, a vision model proposes its attributes, and the user confirms them. Outfit generation is built on a pure domain model that enforces composition rules.

The problem

People own clothes they rarely combine well and have no single place that knows what is in their wardrobe. Manual cataloguing is too tedious, so the app automates classification while keeping the user in control of the result.

What I built

  • Hexagonal wardrobe core (domain / application / ports / adapters) with in-memory fakes for every port, so use cases are tested without any framework.
  • An architecture-guard test fails the build if domain or application code imports React, Expo, Supabase or path aliases, keeping the core portable to Deno edge functions.
  • Database-level ownership guarantees: composite foreign keys (outfit_id, owner_id) and (garment_id, owner_id) make it impossible to compose an outfit from another user's garments; RLS on all tables, anon privileges revoked, verified with pgTAP.
  • Photos never touch Postgres or the app bundle's secrets: edge functions issue short-lived presigned R2 URLs, enforce owner-scoped object keys, and call the vision model server-side.
  • Vision model chosen by an evaluation harness (scripts/eval-vision) with timeout derived from measured p95 latency rather than guessed.
  • CI runs lint, format, typecheck, Jest, pgTAP against a local Supabase stack, and Deno function tests.

Key decisions and why

01

Hexagonal architecture with a Result type

Database, storage and AI providers are swappable adapters behind ports. Expected failures return Result values instead of exceptions, so callers must handle both branches.

02

Cloudflare R2 for images, keys only in Postgres

Zero egress cost and no 1 GB storage ceiling; the database stays small and holds only object keys and metadata.

03

AI calls only from edge functions, behind a swappable port

API keys stay as server secrets, cost is measurable in one place, and the OpenAI-compatible provider can be replaced without touching the app.

04

Ownership enforced by schema, not only by policy

Composite foreign keys make cross-user composition structurally impossible, so a bug in application code or RLS cannot leak data between users.

05

Passwordless email OTP

No passwords to store and no deep-link setup needed for the MVP; social login is deferred until credentials exist.

Tech stack

Languages
TypeScript (strict)
Frontend
Expo / React Nativeexpo-router
Data
Supabase Postgres + RLSpgvector
Backend
Supabase Auth (email OTP)Supabase Edge Functions (Deno)
Cloud
Cloudflare R2
AI
Vision LLM (OpenAI-compatible API)
Testing
Jest + Testing LibrarypgTAP
DevOps
GitHub Actions