All work Full-stack · Cloudflare · Personal project

TripLedger: one booking tracker, isolated per trip.

RoleSolo — design, build, ship
TypePrivate, family-only — no public demo
Year2026
SurfaceFull-stack web app
Bookings grid, desktop Screenshot pending

Problem

A family trip means flights, hotels, and a dozen confirmation emails scattered across everyone's inbox. No one has the full picture until somebody builds a spreadsheet by hand and keeps it updated.

I built the first version for one trip: a Vietnam/Taiwan itinerary, hardcoded to that trip alone — one URL, one guest list, one set of database tables. It worked. But the real problem showed up the moment a second trip came up: nothing stopped that trip's guests from seeing the first one's bookings, because trip wasn't a concept the app had, just an assumption baked into the code.

What I built

  • Multi-trip isolation by URL path. Trips are rows in one D1 database behind one Worker, not separate deployments — but each trip gets its own path (/trips/<slug>/) and its own viewer allow-list, since Cloudflare Access only gates paths, not query parameters. Adding a trip is one database insert plus one Access app, no redeploy.
  • Zero app-side auth code. Cloudflare Access (Zero Trust) with Google as the identity provider handles every sign-in. A fail-safe catch-all Access app on every trip path means an unrecognized trip slug denies by default instead of falling through.
  • Natural-language booking entry. Paste a confirmation email and Claude, via the Anthropic API, returns a structured booking — type, dates, confirmation number, travelers — with a confirm-before-save step rather than writing straight to the database.
  • Attachments the browser can't be trusted to label. Uploaded confirmation PDFs and screenshots are typed by magic bytes, not client-supplied file type headers, and stream back through the Worker rather than a signed R2 URL — so Access still gates every download.
  • Mobile-first bookings view. An inline-editable spreadsheet grid on desktop; a tap-to-open detail sheet and timeline view under 720px. Shipped as an eight-phase project against a Playwright suite that stayed green — 47 tests, two viewports, light and dark — through every phase.
  • Soft delete, hard to misuse. Deleting a booking or attachment sets a reversible flag; the purge route only matches rows already flagged deleted, so no single request can destroy live data.

Stack

Cloudflare Workers Cloudflare D1 Cloudflare R2 Cloudflare Access (Zero Trust) Anthropic API Vanilla JS Wrangler CLI Playwright Claude Code

No framework, no build step: a single Worker serves plain HTML/CSS/JS alongside the JSON API. Wrangler doubles as the admin tool — the same production D1 data a browser writes can be read and edited directly from the CLI, with no separate admin UI to build.

Outcome

TripLedger is live and in use by family across multiple trips, each gated behind Google sign-in with no login code of its own. What started as a one-trip tool generalized into infrastructure: a new trip is a database row and an Access app, not a new deployment.

Rows, not redeploysNew trips added without touching infrastructure
Zero login codeAuth handled entirely by Cloudflare Access
47/47 testsPlaywright suite, two viewports, light and dark