Corsetta
An offline-first progressive web app that runs the full operating cycle of a bridal store, from lead capture to final dress delivery, across four distinct staff roles.
Solo builder: product, architecture, implementation
The Problem
Bridal shops run on a patchwork of spreadsheets, paper forms, and generic CRMs that don't understand the domain. The sale cycle is long and complex: a single customer journey spans initial consultation, appointment scheduling, dress ordering, multiple alteration fittings, payment plans, and final pickup. Each step involves different staff roles with different needs. Existing tools force everyone into the same interface, offer no offline capability (a real problem in fitting rooms with poor connectivity), and treat alterations management as an afterthought. Shop owners have no consolidated view of what's happening across their business.
The Approach
I built a progressive web app that models the bridal shop workflow end to end. The data model follows the customer lifecycle rather than generic CRUD patterns. Every screen is role-aware: a stylist sees appointment booking and client history, a seamstress sees alteration tickets and QC checklists, a manager sees staffing and scheduling, and the owner gets a command center that surfaces actionable data across all operations. The system works offline by default. Staff can book appointments, update alteration status, and log client notes without connectivity. Changes queue locally and sync automatically when the network returns, with conflict detection and resolution built into the sync layer.
Architecture
The frontend is React 18 with TypeScript in strict mode, built with Vite, using TanStack Query for server state and TanStack Router for navigation. UI components are built on shadcn/ui (Radix primitives) with Tailwind CSS. Local data lives in IndexedDB via Dexie.js, with approximately 78 local stores mirroring server-side models. The backend is Supabase PostgreSQL with roughly 95 tables, row-level security enforcing multi-tenant data isolation, and 71 Edge Functions handling business logic. Real-time updates flow through Supabase Realtime (Postgres CDC). External integrations include Stripe for payment processing, Twilio for SMS (10DLC registered), and SendGrid for email. Monitoring runs on Sentry and Better Stack. The offline-first pattern is: read from IndexedDB locally, write locally first, queue the change in an outbox, then sync to Supabase when connectivity returns. Roughly 184 SQL migrations define the schema evolution over the course of development.
Technical Decisions & Tradeoffs
Chose Supabase over a custom backend to move faster on auth, real-time subscriptions, and row-level security. The tradeoff is less control over the data layer, but for reaching a testable product faster the velocity gain justified it. Supabase RLS also gave us multi-tenant isolation essentially for free, which would have been significant custom work otherwise. The role-aware UI operates at three levels: route-level (different pages per role), component-level (same page, different UI rendered based on role), and data-level (RLS filters what each role can query). This created real complexity in UI branching and testing, but the alternative was building four separate apps or forcing every role into one interface that serves nobody well. Stripe integration goes beyond basic checkout. It handles deposits, full payments, payment links, terminal card collection, installment plans with scheduled autopay, refunds, Connect onboarding and readiness checks, webhook handling, and reconciliation. Roughly 15 of the 71 edge functions are payment-related. Building this into the app rather than redirecting to Stripe's hosted UI makes the payment experience feel native to the workflow.
What Broke & What I Learned
The conflict resolution system initially failed on the most predictable scenario: two stylists booking the same appointment slot, one offline and one online. When the offline stylist reconnected, the sync created a double booking instead of detecting the conflict. The fix required version-based optimistic locking. Every update carries an expectedVersion. When the outbox processor syncs, it compares the local version to the server version. If they diverge, the system stores a pending conflict locally, marks the outbox item as needing resolution, and surfaces a review UI where the user can choose to keep their version, accept the server version, or dismiss. Getting this right took several iterations. MFA authentication was a multi-day struggle. The core challenge was not the MFA flow itself but finding the right balance during new user account creation. An owner inviting a new stylist should not force that person through a gauntlet of security steps on their very first login. We went through several breaking changes before finding the right sequencing: let the user in with basic auth first, then progressively prompt for MFA setup once they have context about what they are securing. Getting this wrong meant losing users at the most critical moment, their first impression. Session persistence across PWA installs also required iteration. Supabase Auth uses JWT with a one-hour lifetime and refresh token rotation. The system persists session tokens locally so offline reads and queued writes continue even when the JWT expires. When connectivity returns, it either refreshes the session and syncs normally, or fast-fails with a clear auth error and prompts re-authentication. The important design decision: queued changes are never silently dropped. They remain as visible sync problems until resolved.
Outcomes
The system is in late-stage development and approaching initial testing with real users at a bridal shop. The offline-first architecture has been validated in a real environment with intermittent WiFi, where staff previously could not use any software during fittings. The conflict resolution system handles the most common real-world scenario (overlapping appointment bookings from disconnected devices) without data loss. Payment processing runs the full lifecycle from deposit through installment autopay to final settlement without leaving the app. The role-aware UI means a seamstress opening Corsetta sees alteration tickets and QC checklists, not the full management surface. This reduces training time and cognitive load for each role. The codebase spans roughly 100 data models and 71 serverless functions across a full-stack TypeScript and Supabase architecture.