Noble
Booking SaaS for solo barbershops — 227 commits, one frozen file.
Noble is a multi-tenant booking SaaS for barbershops and salons: $15/month, 5-minute setup, SMS reminders that actually work. Built with Claude over 227 commits, hardened by 16 named bugs, and running autonomous marketing since June.
Next.js 16 · React 19 · TypeScript · Supabase · Stripe · Twilio · Tailwind v4 · 2026-04-08 → present
Context
Noble is the biggest thing I've built with Claude — 227 commits over five months, 151 of them in the first ten days. It's a real product: Stripe subscriptions running, Twilio SMS with A2P 10DLC compliance passing US carrier checks, Supabase row-level security enforcing tenant isolation at the database layer, and an autonomous weekly marketing pipeline pushing content to Instagram every Monday.
Problem
A solo barber makes $40–60 per cut and does 6–8 cuts a day. They lose 20–30% of that to no-shows — bookings taken by phone, forgotten by the client, and never reminded. Fixing that is not a hard engineering problem. It's a nobody-can-afford-to-build-it problem, because the standard tools cost more than the incremental revenue they save.
Three real options exist for a solo shop:
- Booksy ($49/mo) — full-featured, overkill, price kills the ROI.
- Fresha / Square ($99+/mo) — for chains, not solo shops.
- WhatsApp + paper calendar — free, but doesn't scale past ~30 clients.
Noble sits below all three. Same core features (online booking, SMS reminders, staff schedule), $15/mo flat. The trade is: no loyalty program, no marketing dashboard, no franchise features. Solo shops don't need them.
Division of Labor
| Layer | Who |
|---|---|
| Architecture (dual Supabase client, server-first booking page, RLS boundaries) | Me → Claude implements |
| Booking flow logic (slot calculation, cache invalidation, race handling) | Claude, gated by 16 named QA bugs |
API routes (/api/book, /api/reschedule, cron reminders, Stripe webhooks) | Claude |
| SMS/email templates + A2P 10DLC compliance | Me spec → Claude implementation → real Twilio testing |
| Pricing model (drop from 3 tiers to 1 tier) | Me |
| Design system (cream + gold, Apple-style premium) | Me spec → Claude in Tailwind v4 tokens |
| Marketing agents (Strategist → Creator → Designer → Publisher) | Both — my prompts + brand voice; Claude's pipeline |
| Weekly content approval | Me — 1 hour on Mondays |
| Deployment | Vercel autopilot (git push to main) |
Prompt Architecture — Frozen Stable Logic
The single pattern that made this scale past week two: once code is verified, it becomes frozen and named.
The booking-page slot calculation lives in src/lib/slots.ts and SalonClient.tsx. It went through 16 rounds of QA in April. Each bug got a name — BUG-01 through BUG-15 — and a commit hash. All 16 are documented in docs/STABLE_LOGIC.md, which opens with the rule: "Do not refactor the functions listed below. Do not 'improve' them. Do not restructure them. They handle a specific set of edge cases. If they need to change, add a test first, then change with the test as spec."
This is the anti-drift mechanism. Claude, given a codebase without this rule, will helpfully "clean up" a function that survives eight edge cases by removing the special-case handling for two of them. STABLE_LOGIC.md is the sign that says this looks messy for a reason; leave it alone.
The rest of session context is split across single-purpose files: CLAUDE.md (project brief), CONTEXT_MAP.md (session restore), TECH_STACK.md (versions), DESIGN_SYSTEM.md (UI rules), STRUCTURE.md (file map). Loading one loads the right slice. No Claude session reads the whole tree.
Iteration Moment #1 — the April slot-cache freeze
Before: 2026-04-08 → 2026-04-18. Ten days. 151 commits. A booking page that shipped, worked in dev, worked in staging, and started producing double-bookings within an hour of the first real user.
Every failure mode showed up: race conditions on insert (two people booking the same slot because check-then-insert wasn't atomic). Cache stale between browser refresh and server state. Break windows validated wrong. Timezone drift between the browser calendar and the block-timestamps table. Phone regex too permissive. Email accepting garbage.
After: 2026-04-18 16:31 UTC, commit 6b29244 — "fix: all 16 QA bugs — slot cache, overlap, reschedule, break validation, phone/email validation, timezone UTC". 200+ line diff. Each fix numbered. Each numbered fix landed in STABLE_LOGIC.md. No refactor allowed after.
Lesson: AI-collaboration velocity is real, and produces exactly the number of bugs you don't test for. 151 commits in 10 days shipped 16 broken edge cases. QA discipline is the ceiling, not code output.
Iteration Moment #2 — Vercel's cache is not your friend
Before: 2026-06-03. SalonClient.tsx fetched bookings client-side on mount. Vercel's Next.js Data Cache — a layer I didn't know was on by default — held Supabase responses for 1–5 minutes. Slots appeared available that had been booked minutes ago. Race conditions returned at scale.
After: 2026-06-03 15:09 UTC, commit d6e1b5a — "seed bookedSlotsMap from server on page load, eliminating all client cache issues". Plus three sibling commits the same day: bypass Next.js Data Cache on the admin client, force-dynamic on the availability route, sync both bookedSlotsMap and localBookedSlots after every booking. Server fetches 16 days of bookings via supabaseAdmin with cache: 'no-store'. Passes as initialBookings to the client. Client seeds state immediately. Background refresh continues for freshness. Zero cache layers, top to bottom.
Lesson: Managed platforms cache things you didn't ask them to cache. The right response is not to work around the cache — it's to eliminate the cache layer entirely at the point where correctness matters.
Trade-Offs
Supabase over Firebase. Postgres + row-level security lets me push tenant isolation into the database itself. If application code has a bug, the DB still refuses cross-tenant queries. Firestore couldn't guarantee that the same way. Cost: no built-in realtime — solved with server-seed + polling.
Tailwind v4 with no config file. Design tokens live in one CSS @theme block in globals.css. New contributors don't decode a config JS to understand color usage. Cost: complex theme variations (dark mode, seasonal skins) require CSS variable choreography, not JIT compilation.
Server-render fetch, client-render UI. Booking page splits into page.tsx (server, admin queries) and SalonClient.tsx (~650 lines, all interactivity). Security: sensitive queries can't leak to the browser. Cost: two files, prop-drilling coordination.
One $15 pricing tier, not three. The code still has scaffolding for Pro ($39) and Business ($79). I decided not to expose them until a customer explicitly asks for tier-2 features. Simplicity at signup beats incremental revenue per tenant. Cost: hidden upside.
Weekly autonomous content, not manual monthly posts. The marketing_system/ directory is a full agent pipeline — Strategist writes a weekly plan, Creator drafts captions, Designer renders reels + carousels (Playwright + FFmpeg), Publisher pushes to Instagram once Meta API is out of Development mode. GitHub Actions runs the whole thing on free tier. Cost: the creative ceiling is bounded by the agent prompts I've written, not by Claude's raw ability.
Results
- 227 commits over 5 months and 12 days (151 in the first 10 days, 6–11/month after June)
- ~10,000 LOC of application code across 73 TypeScript files
- 16 named bugs documented in
STABLE_LOGIC.md, none regressed since April - 3 architectural pivots with commit hashes anyone can inspect
- 7 reels + 7 feed posts per week, autonomously generated by the marketing system
- $15/mo pricing, 14-day trial, no credit card
- Stack integrations shipped: Stripe subscriptions, Twilio A2P 10DLC SMS, Resend email, Sentry error tracking
Honestly on customers: no public customer numbers yet. Testimonials on the landing page are aspirational. The product is production-ready, the marketing pipeline is running, the funnel from Instagram to sign-up is measurable — but "how many salons pay us $15/month" is not a number I'm ready to put on a page. That's the current work.
Reflection
-
The April sprint proved two things at once. AI-collab velocity is real — 10 days from empty repo to working multi-tenant SaaS is not a lie. And that velocity produces exactly the number of bugs your QA didn't cover. The fix isn't slower code; it's a
STABLE_LOGIC.mdthat names the bugs after you find them, so the next Claude session doesn't unwind the fixes. -
Steady-state isn't done. Noble hit feature-complete around June. Commit velocity dropped 10× — 151 to 6–11/month. But the project isn't paused; it moved layers. Claude now writes marketing content, not code. The interesting work migrated from "build the app" to "build the machine that grows the app." That reallocation is what AI-first solo work makes possible — a traditional team wouldn't reslot this cleanly.
-
The most valuable file in this repo is
STABLE_LOGIC.md, not the code. It's 171 lines. It documents the exact functions that survived edge cases and forbids future refactors. Anything I ship after this will have one — before the first bug fix, not after the tenth.