All work
Consumer & mobile apps · Live in production · 2026

Bilingual therapeutic-riding booking platform

Built for Horse & Harmony

The booking calendar rendered perfectly, and it was empty.

A therapeutic horse-riding practice took bookings by phone and WhatsApp. We built a bilingual Hebrew/English booking site with a self-service admin, then hit a failure mode that produces no error at all.

HE / EN full RTL and LTR
30 days rolling bookable horizon
0 errors when it broke
Interactive. Try it

Drag time forward. Watch nothing break.

Bookable slots are generated into a rolling window. Generation ran once, at deploy.

0
200 OK 0 alerts 0 errors
28 bookable slots

The site was up, the API returned 200, and the calendar rendered correctly, with nothing in it. A correctly-rendered empty calendar is indistinguishable from a quiet week to every monitor you would think to set up. Uptime checks answer did it respond, never did it respond with anything.

The problem

  • Every booking ran through a phone call or a WhatsApp message.
  • Hebrew-first with English, fully right-to-left. Not a translation layer.
  • A non-technical owner: anything needing a mental model of the data would go unused.

What we built

  • A bilingual Astro front end with React islands, full RTL, and an Express + MySQL booking API.
  • Slot generation from recurring templates, one series per active session type, each using its own duration.
  • A pending-first booking flow: submission holds the slot, an admin confirms or declines.
  • An admin panel deliberately kept to the smallest surface that does the job.

Outcome

  • Bookings run through the site, with the owner confirming rather than coordinating.
  • Hebrew and English users get the same product, in the correct direction, not a translated fallback.
  • The horizon is maintained by a daily job, and an empty forward window now pages instead of passing.
Astro 5ReactExpressMySQL 8Cloudflare R2Docker

What something like this costs

It took 10 working days, counted out of the repository rather than estimated afterwards.

With us From $8,000to build, then $450/month
At a mid-market agency $12,000 to $16,000the same 10 days at $200/hr, billed at 6 to 8 hours a day

A rate comparison, not a bill. Our figure is the published ladder from the pricing page, for the closest match to this shape of work. Working days regenerate from the repository on every deploy.

The full write-up problem, constraints, and the whole debugging story. About a 4-minute read

01 The problem

Every booking went through a phone call or a WhatsApp message, which meant double-bookings, no record, and an owner doing scheduling admin instead of running sessions.

The practice operates in Hebrew, with English-speaking clients too, so the site is genuinely bilingual and right-to-left, not an English site with a translation bolted on.

02 Constraints

  • The owner is not technical. An admin panel requiring a mental model of the data would go unused, and the system would quietly revert to WhatsApp.
  • Bookings must never auto-confirm: a slot is held on submission, but a human decides.
  • Session types have different durations, and the schedule is defined by recurring templates rather than by hand-entered slots.

03 What we built

  • A bilingual Astro front end with React islands, full RTL, and an Express + MySQL booking API.
  • Slot generation from recurring templates, one series per active session type, each using its own duration.
  • A pending-first booking flow: submission holds the slot, an admin confirms or declines.
  • An admin panel deliberately kept to the smallest surface that does the job.
The hard part, in full

A failure mode with no error state

Bookable slots are generated ahead of time from recurring templates into a rolling window. Generation ran at deploy, which is fine on day one and wrong forever after: the window advanced, generation did not, and the horizon quietly ran dry.

The result is the worst kind of outage. The site was up. The API returned 200. The calendar rendered correctly. It simply had nothing in it, and a correctly-rendered empty calendar is indistinguishable, to every monitor you would think to set up, from a correctly-rendered calendar on a quiet week.

No exception was thrown, no request failed, and no alert existed that could have fired, because nothing was in an error state. The only signal was the absence of bookings, which looks exactly like ordinary quiet.

The fix is two parts, and the second matters more than the first. A daily job now keeps the horizon extended. And the health check was inverted to treat an empty forward window as a failure rather than a valid response. The system now has to prove it has availability, instead of being trusted because it did not crash.

Got something shaped like this?

Tell us what it is and what has stopped working. We will tell you what we would build.