← Back to projects
Summary

Lustr is an early-stage cleaning and sanitation startup I co-founded, starting with mobile automotive detailing. I own and operate the full software stack end to end: booking, memberships, scheduling, admin tooling, and the ops surfaces behind it. Shipping this to real customers has taught me more than any class project ever could.

Stack

What it's built with.

Frontend

  • React
  • TypeScript
  • Vite
  • Tailwind CSS

Backend & Database

  • Supabase
  • PostgreSQL
  • Row-Level Security
  • Edge Functions
  • pg_cron

Payments

  • Stripe Checkout
  • Stripe Subscriptions
  • Stripe Webhooks
  • Idempotency Patterns

Architecture & Systems

  • System Architecture
  • Concurrency & Locking
  • Transactional RPCs
  • RFM Analytics

Testing & CI/CD

  • Playwright
  • Vitest
  • GitHub Actions
  • Docker
  • Vercel

Documentation

  • MkDocs
Skills exercised

The non-code parts.

Product OwnershipCustomer EmpathyProduction On-CallAgile DeliveryVenture Strategy
Details

How it works.

What this became

Lustr started as mobile automotive detailing, but the software was always designed for a bigger cleaning and sanitation play. I own the software side end to end: architecture, deploys, incident response, and all the weird edge cases that show up once real people start booking in production.

What customers see is a smooth booking and membership experience. What I care about underneath is the reusable core: scheduling, pricing, payments, and ops tooling that can move into new service verticals without rebuilding from scratch.

Scheduling was the hardest part

I thought scheduling would be easy. It was not. The core problem is that multiple people can hit checkout at the same time, and every one of them is competing for real crew time in the physical world. If two carts try to claim the same slot in the same second, one of them has to lose cleanly.

The solution is a slot-hold system backed by Postgres row locks (SELECT ... FOR UPDATE inside the confirm RPC), half-open interval overlap detection, and a 10-minute hold TTL enforced at three different layers — RPC validation, DB cleanup on every availability read, and a pg_cron job that sweeps stale rows. Crew capacity is a first-class concept: a slot is only unavailable if the number of overlapping jobs meets or exceeds available crews. That means the same engine scales from one crew to many without rewriting anything.

The pricing engine

Pricing isn't flat. A detail on a full-size SUV takes longer and costs more than the same detail on a sedan, and members get different rates than walk-up customers, and some services aren't even visible to non-members. I modelled this as vehicle → vehicle_type → group_slug → service_pricing, which sounds pedantic but means I can tweak pricing without touching code.

Every quote is recomputed server-side. The client never tells the server what to charge — the server looks at the hold, resolves the vehicle group, applies membership benefits only if the booking falls inside the current Stripe billing period, and hands back a number. That last check matters more than it sounds: without it, someone could book a service six months out at member rates and then cancel their membership.

A CRM that actually drives decisions

The admin side runs a customer health score using an exponential-decay RFM model — recency, frequency, monetary with a ~139 day half-life. It layers in booking velocity (linear regression on inter-booking intervals, so we can tell if a customer is speeding up or slowing down), and cart-value-weighted abandonment penalties (a $50 abandoned cart from someone who usually spends $500 is different from a $400 cart from a $100-average customer).

The point of all this isn't dashboard theatre. It drives real decisions — who gets the retention email, who's about to churn, who's using their membership and who isn't. It's one of the bits I'm proudest of because it's the difference between 'we have a CRM' and 'our CRM is earning its keep.'

Stripe integration complexity

Payments go through Stripe in two flavours — Checkout Sessions for one-shot bookings and carts, and Subscriptions for memberships. The webhook handler listens for about ten different event types and has to be idempotent, because Stripe will happily redeliver the same event if you're slow to ack. One detail I'm glad I got right early: on failed payments, I source appointment metadata from the slot_holds table rather than from Stripe metadata, because Stripe metadata is technically tamper-able.

How we ship safely

Three-tier: dev/* → stg → main. Gate 1 runs on every PR into stg — Vitest plus a smoke Playwright suite against a fresh Docker Supabase instance. Gate 2 runs on PRs into main and runs the full Playwright suite, which is currently ~40 specs covering booking, checkout, scheduling, admin, and auth end-to-end.

The part I think is actually clever: when main gets updated, deploy-prod.yml pushes migrations to production, posts a prod-migrations commit status, and Vercel's deployment check blocks the production build from promoting until that status goes green. No more new frontend talking to old schema.

Testing discipline

A little over 100 Playwright end-to-end specs and 250+ Vitest unit tests. The E2E tests are the ones that have earned their keep most — they've caught real regressions that unit tests wouldn't have, especially around Stripe webhooks and the hold-confirmation state machine. I also wrote an MkDocs handbook that covers the end-to-end workflow, so when future-me (or a new engineer) comes back after a break, the first stop is a document, not a git log.

Highlights

The things I'm proudest of.

  • Built and shipped a full-stack platform on React (Vite), Supabase/Postgres, Stripe, and Vercel that now runs real customer bookings and payments.
  • Implemented a conflict-aware scheduling engine with slot holds, overlap detection, crew-capacity logic, and DB locking to keep booking state consistent under concurrent checkout traffic.
  • Built a full booking + membership flow: draft-persistent multi-step wizard, cart checkout, recurring subscriptions, and customer-facing account management.
  • Designed an admin CRM with revenue views, funnel visibility, and customer-health scoring (RFM + velocity + abandonment weighting) to drive retention actions.
  • Set up CI/CD and release guardrails with staged environments, migration-gated deploys, and automated edge-function/database rollout paths.
  • Backed the system with strong regression coverage: 100+ Playwright E2E tests and 250+ Vitest unit tests across booking, payments, auth, and scheduling paths.