[CODE]

The Third State That Costs the Owner Nothing to Set

A B&B availability calendar had two states: open and booked. The owner needed a third — dates locked for holidays that route guests to a phone call. The hard part wasn't the state. It was making it impossible for a busy owner to trigger the wrong one.

6 min read AI-generated
astro typescript bnb ux system-design

The availability calendar on this B&B site had exactly two states: open, and booked. Sage green, wood-grain stripes. It reads the owner’s Google Calendar over ICS, keeps only the start/end dates of each event, and throws everything else away — no titles, no attendees, no descriptions. That deliberate blindness is the privacy guarantee: the source calendar holds real guest names because the owner uses it to track actual bookings, and the module was built so none of that could ever leak onto a public page.

Then the requirement changed. Over long holiday weekends the owner doesn’t want to sell single nights — the property is whole-house rental, and a lone Saturday between two bookings is dead inventory. He wants those dates locked, but not shown as “booked.” He wants them shown as ask us — a signal to pick up the phone or LINE so he can hand-screen the request and maybe stitch together a longer stay.

That’s a third state. The interesting part is not the state — it’s where it comes from.

Why not a second calendar

The obvious implementation is a second ICS feed per property: a “blocked dates” calendar layered on top of the bookings calendar. It’s clean in code. It’s a disaster in operations.

This brand runs two buildings, and the owner already juggles booking calendars across the fleet. A second calendar per property means four more feeds to wire up, four more environment variables, and — the real cost — the owner switching between calendars in his phone every time he wants to block a date. The system that closes the loop between a website and a real booking only works if the owner actually uses it daily. Every extra calendar he has to remember to open is a reason not to.

The second rejected option was subtler: Google Calendar has a per-event “show me as free/busy” flag (TRANSP in ICS terms). We could read that and treat “free” events as the locked state. But that flag is buried three taps deep in the event editor. An owner who’s blocking a holiday weekend between two guest phone calls is not going to reliably find it, and when he forgets, the failure is silent.

The whole mechanism is one word in a title

The design that shipped: same calendar, same daily workflow. To lock a date, the owner creates an all-day event whose title, trimmed of whitespace, is exactly one word — the same word the website shows for that state. Anything else on the calendar is a real booking.

That last sentence is the entire safety argument. The keyword match is a boolean test — title equals the keyword, or it doesn’t — and the result is discarded the instant it’s computed. We now read the SUMMARY field, which the module previously refused to touch. That’s a deliberate relaxation of the privacy rule, documented as such: we read the title, run one exact-match comparison, keep the boolean, and drop the string. Description, location, attendees stay unread. The privacy guarantee narrows by exactly one word’s worth of surface area, and no further.

The direction of failure is the point. If the owner fat-fingers the keyword, the event doesn’t match — so it falls through to the default and shows as booked. A typo undersells: a date that should have said “ask us” instead says “full.” It never oversells. There is no keyword typo, no encoding quirk, no trailing-space bug that can turn a locked holiday date into a bookable one and put a guest’s card through for a night the owner didn’t want to sell. Booked outranks locked outranks open, and every ambiguity resolves downward.

The word had to be the word on the screen

The first cut used a keyword that described the action the owner takes — a lock instruction. It went through three revisions. The version that stuck makes the keyword identical to the label the guest sees on the calendar cell.

That’s not a cosmetic choice. The owner has to remember exactly one string, and the way he remembers it is by looking at his own website. The word he types into the calendar is the word printed on the tile. There is no translation table in his head between “the thing I do” and “the thing guests see.” One word, one meaning, visible in the place he’d naturally look to check whether it worked.

The copy itself also moved. It started as a phone-only instruction and opened up to “call or LINE us” — LINE being how most of these guests actually reach the owner. The keyword tracked that change so the two never drifted apart. Whatever the label says on the screen is what the owner types. If the label changes again, the keyword changes with it, and the owner still only has to remember what’s in front of him.

The visual state carries its own weight

Open is sage. Booked is wood-grain stripes. The locked state is honey/amber, with a dotted border and the word spelled out in the cell — foreground on background clears about 6.3:1, comfortably past AA. The colour is not doing the work alone: the dotted border and the text label both mark the state, so a guest who can’t distinguish the hues still reads it correctly. Three states, three encodings that don’t rely on colour perception.

Why this is the right shape

Step back from the diff. The engineering here is trivial — a string comparison, an enum with three values, a CSS custom property. What makes it correct is that every decision bends toward the constraint that isn’t in the spec: the person operating this system is running a B&B, not a booking platform, and he’ll use exactly the workflow that costs him nothing new to learn.

Zero new calendars. Zero new apps to open. One word, and it’s the word already on his screen. Every failure mode routed toward the safe direction. That’s the whole design, and it’s the reason it has a chance of actually being used — which, for a system whose entire value is that the owner keeps it current, is the only measure that matters.

Whether it holds comes down to friction. The bet is that typing one familiar word into a calendar he already opens every day is low enough friction that there’s no reason not to.