02 Own project
RegenerateLife
An information site for people in the United States who are reading up on regenerative medicine, and the console the care navigators work in behind it. The public pages are prerendered React; the console is drawn on the server by the same small Node service that stores the enquiries.
- Role
- Own project. Site, console and server.
- Built with
- React and Vite (prerendered), Node, append-only JSON event logs, server-drawn SVG maps
- Runs on
- nginx behind Cloudflare, nightly restic backups
- Live since
- 17 October 2026
- Address
- regeneratelife.co
Problem
Every introduction has conditions
RegenerateLife puts people in touch with vetted partner clinics. Before a navigator can introduce someone, three things have to be true: the person was reached, they heard the required disclosures on that same contact, and they agreed, separately, to their details being shared. Afterwards someone has to be able to show what was said and what was offered.
Constraints
Rules a button can’t skip
The rules can’t live only in the page, where an old tab or a hand-made request would get around them. Nothing about an enquiry should go to a third party just to draw a map. And all of it has to run on the same small server as the public site.
What I built
One writer, one log
One Node service stores the enquiries and draws the staff console on the server, and it is the only process that writes those files. Every action in the console appends an event; the state of an enquiry is whatever replaying its events produces. Nothing is edited in place, so the log is also the audit trail: which disclosures were ticked, what consent was given, which clinics were offered and in what order, and which version of the call script was on screen.
The routing rules run on the server. The console only offers clinics that are fully vetted and active, inside the search radius, nearest first. The page decides which buttons to show; the server decides whether an action happens.
No order or payment data in the console, by design.
The maps in the console are SVG, drawn on the server from public Census ZIP-code areas, GeoNames and us-atlas data that ship with the service. No tile server or geocoder ever sees where an enquiry comes from.
How it works
Replay an enquiry
The introduction rule, run in your browser against a made-up enquiry. Move the slider or pick an event to see what the console would allow at that point.
-
The website form arrives, with consent to be contacted.
Introduction: blocked, nobody has been contacted yet.
-
Call 1: no answer.
Introduction: blocked, contact 1 did not reach the person.
-
Call 2: reached. The call drops before the disclosures.
Introduction: blocked, no disclosures on contact 2.
-
Call 3: reached.
Introduction: blocked, no disclosures on contact 3 yet.
-
The navigator reads the disclosures on call 3 and ticks them.
Introduction: blocked, no consent to share details.
-
The person agrees to their details being shared with a clinic.
Introduction: allowed.
-
The console offers clinics: fully vetted, active, inside the radius, nearest first. The list is saved with the event.
Introduction: allowed.
-
The introduction is sent.
Introduced, and recorded as an event like everything else.
What broke
Two forms that said yes
The public form showed its confirmation even when the server refused the enquiry. A phone number typed without its area code (the server wants 10 to 15 digits) came back as a 400, and the person still read that their request was in. Now the confirmation appears only after the server accepts, and the field checks the same pattern before it sends.
The staff console went out with Referrer-Policy: no-referrer. Under that policy Chrome sends Origin: null with every form post, so the console’s origin check turned away setup, sign-in and every action. The scripted tests set the Origin header themselves and the page checks never submitted a form, so all of them passed. The console now sends same-origin, falls back to the Sec-Fetch-Site header when Origin is null, and a browser test types into the forms and submits them with real clicks.
Two smaller ones. Pages reached through a link kept the landing page’s title, because the prerender set titles per route and the router didn’t; one map of titles feeds both now. And HTML goes out with Cache-Control: no-transform, so Cloudflare’s email obfuscation can’t swap mailto links for a placeholder that only works with JavaScript.
What changed
Live, with the console behind it
The site went live on 17 October 2026 with the console behind it. Every push runs lint, a type check, the build gates (banned words in the copy, colour contrast against the design tokens) and the enquiry service’s tests.