Angel Campa
Colour theme

WorkThe April wave

Kaiplan

A wedding planning workspace with a budget ledger, guest list, seating chart, vendor tracker, and a public wedding website guests could RSVP to without an account.

Ran on
Cloudflare
Ran
April 2026 to June 2026
Domain
Wedding planning
Source
wedding-planning-saas-kaiplan-snapshot

Built with

  • Cloudflare Workers
  • Hono
  • React 19
  • Astro
  • Neon Postgres
  • D1
  • Drizzle

Hosted services

  • Cloudflare Workers
  • Hyperdrive
  • D1
  • Durable Objects
  • R2
  • Cron Triggers
  • Turnstile
  • Neon Postgres
Dashboard with a 288-days countdown beside the wedding name and date, three quick-action buttons, and a first-steps panel reading 7 of 7 complete above budget and guest-list summary cards
The dashboard for a seeded demo wedding, captured from the local stack against seeded data.

The decision

A seating chart was one jsonb column per wedding, and every seat inside it held a guestId pointing at the normalized guest table: a foreign key Postgres could not see or enforce inside a JSON blob. A guest could also decline from the public wedding website without ever logging in, so the row that invalidated a seat could change from outside any session.

Four mechanisms enforced the invariant the schema could not express: a zod refinement blocking a chart that seated the same guest twice, a transactional re-read on save that rejected a stale reference, a self-repairing read that dropped assignments for guests who had since declined or been deleted, and seven call sites that stripped a guest out of the chart in the same transaction that removed them.

The call site that mattered was the seventh: the public RSVP handler, reachable with no session at all, behind a rate limiter and a Turnstile check. That route lived inside apps/api/src/routes/wedding-website.ts, 1,559 lines carrying most of the guest-facing surface, and it sat outside the per-file coverage gate: the highest-risk call site and the thinnest test evidence sat in the same file.

Architecture

Three Cloudflare Workers sat in front of two databases and one Durable Object: a React SPA for the couple, an Astro site for guests and marketing, and a Hono API between them. Postgres, behind Hyperdrive with a pool capped at one connection, held the transactional product data: weddings, guests, budgets, vendors, seating charts.

A separate Cloudflare D1 instance held marketing signups, pricing clicks, and email preferences, bound into both the product API Worker and the marketing Worker, so a transactional product email could honor an unsubscribe made from a marketing footer without either Worker touching the other’s primary store.

Stripe webhook verification ran through Stripe.createSubtleCryptoProvider(), since the Workers runtime has no Node crypto module to verify signatures the usual way. Five nightly maintenance jobs, scheduled through Cron Triggers, ran strictly in series to match Hyperdrive’s single-connection pool, each job holding that one connection until it finished before the next began.

By the numbers

Each figure carries where it came from and when it was measured. Open one to see the command behind it, where the repository recorded a command.

Application source
230,851 lines across 981 files
Provenance

Source portfolio/METRICS.md § 1. Lines of code

git ls-files | grep -E '\.(ts|tsx|astro|css|sql|mjs)$' | xargs cat | wc -l

Measured

Source vs test
89,693 source, 141,158 test (test outweighs source 1.57 : 1)
Provenance

Source portfolio/METRICS.md § 2. Source against test

Measured

Test cases
6,929 across 425 test files, 13,758 assertions
Provenance

Source portfolio/METRICS.md § 3. Test files, cases, assertions

git ls-files | grep -E '\.(test|spec)\.(ts|tsx)$' | xargs grep -cE '^\s*(it|test)(\.\w+)?\(' | awk -F: '{t+=$2} END{print t}'

Measured

Coverage gate
95% per file (lines, functions, branches, statements) across the unit-tested code in all 8 workspace packages
Provenance

Source portfolio/METRICS.md § 4. The coverage gate

Measured

End-to-end
24 Playwright specs, 96 running cases, across 3 device profiles
Provenance

Source portfolio/METRICS.md § 5. End-to-end

git ls-files | grep -cE '^e2e/.*\.spec\.ts$'

Measured

API and data surface
79 HTTP endpoints across 14 route modules; 20 Postgres tables over 25 migrations; 9 D1 tables over 14 migrations
Provenance

Source portfolio/METRICS.md § 6. Surface and data

git ls-files 'apps/api/src/routes/*.ts' | grep -v test | xargs grep -hcE '^\s*app\.(get|post|put|patch|delete)\(' | awk '{t+=$1} END{print t}'

Measured

Marketing content
247 Markdown entries across 6 Astro collections (133 guides, 29 listicles, 26 comparisons, 24 pricing breakdowns, 19 alternatives, 16 lead magnets)
Provenance

Source portfolio/METRICS.md § 6. Surface and data: The 247 marketing entries

git ls-files 'apps/web/src/content/**' | sed 's|apps/web/src/content/||;s|/.*||' | sort | uniq -c

Measured

History
469 commits, 2026-04-07 to 2026-07-08, 1 contributor
Provenance

Source portfolio/METRICS.md § 7

Measured

Screens

Seating chart with a guest card, Carlos Mendez, held mid-drag above a round table of six named seats; a guest rail on the left lists unseated guests and an inspector on the right reads 12 assigned seats, 24 empty, 4 unseated
Seating chart. A guest held mid-drag over the canvas. Eight tables, twelve seated. This is the screen the seating chart decision above is about.
Budget page with a $35,000.00 total against $32,920.00 quoted and $14,100.00 paid, and five category cards each showing quoted against allocated with a fill bar
Budget ledger. Categories, quoted against paid, progress.
Guest list of 18 guests (12 confirmed, 4 pending, 2 declined) as a table with side, group, and an RSVP pill per row reading Accepted, Invited, or Declined
Guest list. RSVP states, sides, groups. A declined row here is what invalidates a seat.
Index page listing wedding-planning tools grouped under three headings (Alternative to, Head-to-head, and Pricing explained), each row a linked comparison with a one-paragraph summary
The comparison hub. 69 entries in three collections, routed by compare/{alternatives,versus,pricing}/[slug].astro.
Read the repositorywedding-planning-saas-kaiplan-snapshot

Its portfolio/ directory holds the architecture, metrics and testing documents these figures were taken from.