01 — Booking platform
Meridian
Meridian books appointments for a fictional clinic with three practitioners, four services and variable durations. Service, practitioner, date and time are four steps and four URLs, so the back button works and a slot can be sent to somebody in a link. The interesting part is not the flow. It is what happens when two people want the same 09:30, and what the screen is allowed to say when the database goes away mid-write.
- Next.js
- React
- Postgres
- Drizzle
- Tailwind
Measured on the finished build
- Tests
- 69 passing, 8 files, 0 skipped
- Database down
- 9 page routes render real content, none reached the framework handler
- JavaScript off
- 9 of 9 page routes render, 308–511 characters each
- Axe violations
- 0 at 1280 and at 360, 8 routes
- Contrast
- 25 pairings against WCAG 2.2, all holding
- CLS
- 0.0000 on 8 routes across 40 runs
- Lighthouse
- 99 / 100 / 100 / 100 / 100, mobile, lowest of five runs
The hard part
Two people, one 09:30
Every booking system has the same race in it. Two requests read an empty slot, both find it free, and both write. Checking for a conflict in application code narrows the window without closing it — between the read and the write there is always room for the other request.
The second, less obvious version of the problem is the failure that lands in the middle. If the connection dies while COMMIT is in flight, the appointment either exists or it does not, and nothing running in the web process can know which. Telling somebody “nothing happened” is the sentence that makes them book twice.
What I did
Make it something the table cannot hold
Overlapping appointments are refused by a Postgres exclusion constraint — EXCLUDE USING gist over practitioner and time range. A retry that discovers the first write landed is refused by the database and comes back as slot_taken. Two confirmed appointments in one slot is not a thing the application avoids; it is a thing the schema makes impossible, and the concurrency test proves it against a real Postgres rather than a mock.
That constraint is also what lets the error copy be honest. The write path splits three ways — before the insert, at the insert, and after it — and each one says only what is true at that point. The middle case says plainly that it is not clear whether the appointment was made, gives a check that works in this build (a confirmed booking removes its time from the grid), and says that booking again is safe. It is safe because of the constraint, not because of the wording.
Decisions, with the price attached
What was chosen, and what it cost
No streamed loading fallback, anywhere
The booking flow works with JavaScript switched off, and a streamed fallback breaks that. Measured: with a loading.tsx on the staff page and scripting disabled, the page renders 38 characters — the fallback — and stops, with the real schedule sitting in a hidden div waiting for an inline script that will never run. Without it the same page renders 541 characters of real content.
The cost — No skeleton on a slow first paint. A fallback that hides the page from the readers it was written for is worse than no fallback.
The 404 will not tell you whether a reference exists
A booking reference is the only credential in the build. “There is no appointment with that reference” would turn the 404 into an enumeration oracle over other people’s appointments — four characters of a 32-symbol alphabet is a million-wide space worth scripting against. It points at the confirmation email instead.
The cost — Slightly worse for the honest reader who mistyped a character. Written into the file rather than left to be discovered.
Two framework behaviours measured, not assumed
An error boundary does not server-render in Next 16.3.1: a throw during SSR is answered with an empty body and the boundary appears on hydration. With scripting off that is a 500 and zero characters of body text. notFound() behaves the same way once a route has started rendering. Both are recorded in the source, because both contradict something that would otherwise have been written down as true.
The cost — Neither was worked around. The workaround for the second trades a blank page for a lie about the status code.
Stated rather than left to be found
Known limits
- The booking reference is the only credential, and a reference discloses the name, email and phone on that booking. There is no rate limiting. It is a demo, it is disclosed in the README, and it is the first thing I would change for a real clinic.
- The seeded fortnight has a fixed end date and needs reseeding to stay populated.