Ryan Gilpatric

AI agents write the code. I decide what ships.

The work

Corsetta's owner brief on an iPad: what needs the owner this morning, from fittings to confirm to pickups waiting, over the boutique's numbers for the week and the month.

Corsetta

An offline-first bridal-shop app currently in pilot, built around appointments, alterations, payments and pickup.

The case study

OpeX Liquidity in a desktop window, showing one stock, UiPath: its daily prices since March drawn over the options contracts open at each strike price, calls in orange and puts in blue, the largest listed down the right with their expiries.

OpeX Liquidity

Open interest is usually shown as a snapshot. OpeX Liquidity shows it as a history: every strike in a stock's option chain, day by day, drawn around the price. You can watch walls of positioning form, grow and fade, and every contract stays in the history even after it expires. Time stays honest too. Nothing appears before the day it was known, so any past session replays exactly as it stood then.

The case study

Writing

  1. When Execution Gets Cheap, Direction Gets Exposed
  2. When the Answer Sounds Finished
  3. How I Build with AI Agents
  4. Building a Market Strength Dashboard: The Full Story

How I work

  1. Start with the person using the product
  2. Turn the outcome into a bounded assignment
  3. Give implementation a clear owner
  4. Review from a separate context
  5. Prove the complete change
  6. Release into the real system

The whole method

The staircase in its concrete lightwell, seen from the one viewpoint where its four flights close into a loop that climbs forever.

Let’s build
something together.

hello@ryangilpatric.com

Or write me a note

Ryan Gilpatric

Corsetta

An offline-first bridal-shop app currently in pilot, built around appointments, alterations, payments and pickup.

Role
Solo builder: product, architecture, implementation
Stack
React 18, TypeScript, Vite, Supabase, Dexie.js, Stripe, Twilio, TanStack Query, Tailwind CSS
Signals
offline-first, role-based access, conflict resolution, payment processing, real-time sync, PWA
On file
95 data tables, 184 schema revisions, 4 staff roles modeled
Corsetta's owner brief on an iPad: what needs the owner this morning, from fittings to confirm to pickups waiting, over the boutique's numbers for the week and the month.

The Problem

Most bridal shops run on industry systems designed years before shops worked the way they do now. 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. The established tools give every role the same interface, work only while connected (a real limit in fitting rooms with weak WiFi), and treat alterations as a side feature. None of them gives an owner one view across the whole cycle.

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 to reach a working product sooner, 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 risked 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

Corsetta is currently in pilot. I built the app around the bridal-shop workflow, with role-specific screens, local writes and queued synchronization. When an offline booking disagrees with one made online, conflict checks flag it for a person to resolve.