ENGINEERING · June 16, 2026
Vibe coding a website for my parents' clinic
Two hours into building a website for my parents' hospital, I made what turned out to be the most important commit of the project. It didn't add a feature. It deleted the fake data.
Adinath is my family's hospital — not a metaphor, not a portfolio prop. Adinath Multispeciality Hospital in Shahibaug, Ahmedabad: orthopedics and gynecology since 1990, run by my parents. Dr. Ashok Sajnani, joint surgeon, thirty-five years. Dr. Sunita Sajnani, OB/GYN, thirty. Real patients book real appointments, and the phone number on the contact page rings a real desk.
I built its website mostly on vibes, at speed, with an AI pair — and then spent four months learning, in public, in the git log, which of those fast decisions survived contact with reality and which ones sent a bill. This is the honest version, reconstructed from the commit history rather than from memory, because memory flatters and git log doesn't.
The numbers first, because they shape everything that follows: 236 commits between December 20th and April 6th — but only 22 active days. Four of those days hold 72% of everything. Real side-project work isn't drizzle; it's storms, with long clear stretches between them. The story of this site is the story of what each storm was for.
Night one: fast, and honest about it
The clock starts December 20th, 19:37. First commit: public pages. Forty-nine minutes later there's a booking flow, a medical store, SEO tags, and a PWA manifest. Two and a half hours in, there's a complete hospital-management system — login, three role-based portals, forms, the works. This is vibe coding at its actual strength: an entire product's shape exists before midnight.
Two things about that night matter more than the speed.
At 22:42 — the commit I opened with — I deleted the placeholder content: "honest stats … remove fake data" — and put the real receptionist’s name where the invented one had been. I wasn't building a demo with lorem ipsum and stock-photo doctors. The instinct to put the real hospital's real people and real numbers in was there on night one, and it's the thing that separated this project from a throwaway.
And at 23:03, and twice at 23:09, there are three commits whose only job is cleaning merge-conflict markers out of the live page. Speed puts real doctors online by ten o'clock and <<<<<<< HEAD in the trust section by eleven. Both facts are the same fact — that's what "give in to the vibes, forget the code exists" looks like in a log.
December 21st was the peak: first commit 00:03, last 23:32, ninety-one commits in between — role-based guides, WhatsApp share links, a mobile nav, and a hard push to "100% test coverage" and "100% E2E pass rate." Remember that number. It doesn't survive.
Five weeks of silence
From December 30th to February 5th, the log holds exactly one commit:
deps: bump lodash from 4.17.21 to 4.17.23 (#22)
A bot. For five weeks the repository's only heartbeat was a dependabot PR. I used to read gaps like that as failure. I've changed my mind: the site was live, the family was using it, and nothing was on fire. A gap where nothing breaks isn't abandonment. It's the first evidence that the thing holds up without you.
The two bets I baked in while moving fast
Two architectural decisions got made in the December blitz, both for the same reason: I reached for the tool already open on my desk.
Bet one: translate in the browser. The hospital serves three languages — English, Hindi, Gujarati. In Ahmedabad that's not a nice-to-have; it decides whether a patient can read the booking form or bounces. I built i18n by tagging DOM elements with data-i18n attributes and swapping the text at runtime, persisting the choice in localStorage. Felt pragmatic.
Bet two: React for everything interactive. Every page that did something — booking, signup, status check — became a React island. Felt like the modern default.
Both bets ignored the same truth, and I didn't see it until the day I changed frameworks.
The afternoon it all came due
The site came back to life in February with a visibly different character — conventional commits, deliberate scopes, security hardening, a real booking wizard. And a theme: February 8th brought a warm redesign — forest green, cream, Playfair Display — an eight-file commit that went on to drag a thirty-five-commit tail of stylesheet work behind it. The first version had looked like a tech demo, and hospitals shouldn't feel like SaaS landing pages. The pivot commit was small; the conviction was the tail.

But the afternoon that actually taught me something was March 18th — the day the whole site moved to Astro. The timestamps tell it better than I can:
- 02:56 — scaffold the migration. The biggest commit in the repo: 68 files, +10,158 lines and −37,423. A migration is mostly demolition; the insertions are the small half of the diff. Both bets come along for the ride.
- 16:31 — first crack. "Translations were empty objects because
loadTranslations()was never called." The runtime approach is failing on a static build. - 17:05 — I double down. I bolt a bespoke runtime translator onto the static site to save the runtime translator.
- 18:08 — I reverse it. Completely. Build-time i18n: each page generates
/en,/hi,/guvariants with the translated text baked into the HTML. Zero runtime JavaScript for translation. The React switcher dies, the forms become plain HTML plus afetch, and about nine hundred lines of component code disappear.
One hour and three minutes from doubling down on the wrong idea to deleting it.

The bug that forced the reversal is worth stating plainly, because it's the kind only a real user hits: click Gujarati, and the navigation translates but the booking form right below it stays in English. Half the page switches. It looks broken on exactly the page where a patient is about to hand a hospital their name and phone number. And the root cause wasn't a missing event listener — it was that I had two sources of truth for "the current language" and no plan for them to agree. A data-i18n DOM system and a React hook system aren't two implementations of i18n. They're two different applications sharing a page.
Bet two died in the same commit. The interactive pages had been shipping roughly 360KB of JavaScript each — React plus a database client — so that a user could type their name into a form. A form doesn't need a component tree. It needs an HTML <form> and a fetch. The fix was about 3KB a page.

Strip the specifics away and both bets were the same mistake: doing at runtime, per user, what could be done once at build time. I'd had the modern default backwards — interactive by default, static as the exception — and the framework quietly let me, then sent the bill to the slowest phone on the worst connection in Ahmedabad.
The tests I was proud of
Here's the part that's easy to leave out. On December 21st I hit "100% test coverage." Today the repo has zero test files.
They didn't fail; they rotted — quietly dropped somewhere in the churn of two framework migrations and a redesign. And the sharp bit: no test ever covered the i18n layer. Not one. The most user-visible failure in the whole project — the Gujarati-nav-English-form bug — shipped and survived precisely because the one dimension that made the site matter to its users was the one dimension nothing tested. I had 100% coverage of what was easy to test and 0% of what was load-bearing. That's not a testing failure. It's a failure to point testing at the risk.
What would have caught it is almost embarrassing: a five-line check that fails the build when Hindi or Gujarati is missing a key that English has, and one browser pass per language. On a trilingual site, the bug that ships isn't a broken function. It's a missing translation.
What the ledger says
The final commit-type distribution: 54 fix, 45 feat. More fixes than features — and there was a time that ratio would have embarrassed me too. Now I read it as the signature of something that got used, not just built. A broken contact link on a clinic page isn't a bug; it's a patient who couldn't reach a doctor. The fixes are where the site earned the right to exist.
(And the hundred commits with no type at all? Every one of them is from December. From January 1st onward, nothing shipped untyped. Discipline arrived the way it usually does — quietly, at the moment the sprint became a thing I maintain.)
The last storm, March 28th, was 26 commits of pure product — structured data, enriched doctor profiles, photo galleries, a WhatsApp deep link so a patient can start a conversation in one tap. Then the log settles, and the project ends the way real projects end: three commits of SEO polish. Canonical trailing slashes. hreflang codes. Rich snippets. Not features — the sound of a thing reaching done for now.
One honest scoping note, because a security engineer should draw the line: the site handles booking-form PII behind row-level security and HTTPS, with no secret keys in the browser. That's a defensible posture for what it is, and it is not HIPAA compliance, which needs a signed BAA and a very different budget — I won't imply otherwise. The site isn't revenue-generating, either. It's a real thing for real patients, and also, honestly, proof that I can take a family business online properly. That's its own kind of stake.
The lesson I'd keep
The default you don't think about is the one that bills you later. Vibe coding is very good at generating defaults very fast — an entire product before midnight, the easy 70%. Turning that into an actual product, one a patient's parents can use, in their own language, on a cheap phone, is the last 30%: the slow, honest work of noticing which defaults were wrong, and having the nerve to delete them.
Look back at this log and the deletions are the story. The fake data, at 22:42 on night one. The runtime translator, at 18:08 on March 18th. Thirty-seven thousand lines in the migration commit. The tests that tested the wrong thing. Every important moment in four months of history is something being removed — which is the part of building nobody puts in the demo.
I spend my days on trading and security systems, where a wrong default costs milliseconds and money. Adinath is the opposite end of the spectrum: a static site for a family hospital, where a wrong default costs a patient who can't read the booking form in their own language. Same lesson in both worlds. This account is reconstructed from the git history; corrections — especially from my parents, who know the hospital better than the commit log does — are welcome.