Building & Shipping

Two Things That Look Identical On A Calendar

Brett Ridenour Brett Ridenour · Published November 2026

Open any booking site. Pick a tour, a boat, a table, a photographer. The next screen is a calendar, and some of the days are greyed out. The customer never asks why. The grey square means “pick another day.” That’s the whole affordance.

Inside the system, that grey square is at least three different things. Getting them confused is one of the fastest ways to put weird money in the wrong column of a ledger.

Here’s what the enum actually looks like on the Freebo admin side today:

The admin status enum that drives every grey square on the calendar

Four words, and each one wires into a completely different pipeline downstream. available is the obvious one. The other three are where it gets interesting.

sold_out is a revenue statement

When a day is sold_out, something is booked. That means a quote version was minted, a payment intent was created, money moved (or is pending), a reservation row exists, and a POST_RESERVATION_CHARGE event has already been handed to the ledger worker. Formance has started double-entry-posting the receivable. Revenue-forecast pipelines are going to see this day on their next tick.

sold_out is loud. It touches everything.

limited is a capacity statement

limited means “someone is on this day, but not all the seats are gone.” The grey square isn’t even grey — it’s partially grey. It exists because public tours and private charters run the same product differently: a six-seat kayak tour at half capacity is still sellable to a stranger, not sellable to the party of eight who wanted the whole boat.

The interesting part is that limited is derived, not stored. Nobody writes limited anywhere. You compute it every time by asking the reservations table “how much of this slot is taken?” and comparing against the product’s max capacity. If a slot has one booking, it’s limited. If it’s full, it’s sold_out. If a booking cancels, the same slot flips back to available without anyone writing a thing.

blocked is a wish

blocked is the quiet one. blocked means: nobody bought anything, but the slot is unavailable anyway, because the operator wants it to be. Weather. Engine maintenance. A family wedding. A Tuesday that the captain is just done with.

The design question is what “wants it to be” actually means in the database. The lazy answer is: insert a fake reservation with status blocked, zero price, no customer, and let the availability cache do the rest. That works. It also lies. Now your reservations table has rows that aren’t reservations, your revenue-forecast query has to filter them out, your booking-count metric is wrong, and six months from now somebody writes a SQL migration that assumes every row in reservations points at a real human and the whole thing lights up.

The better answer is a separate object that participates in the availability pipeline but not in the revenue pipeline. A block-off is an event, not a reservation. It can carry a reason (weather, maintenance, off), a creator, a timestamp. It can be deleted without a refund. It can span a day or an hour or a week. It never gets a quote version, never emits a ledger event, never shows up in a revenue report.

Two things that look identical on the surface, but one of them owes money and the other one doesn’t.

— the only thing that matters

The sneaky third category: a hold

There’s a fourth state that doesn’t live in that enum, and probably should. A hold is when someone has started checking out but hasn’t paid. The slot isn’t available — their payment intent is live, their cart is mid-flight, the quote version is written. But it isn’t sold_out either, because the money hasn’t moved.

Every booking system with a payment intent has this problem. Stripe recommends treating a payment intent like a 15-minute lease on inventory. Which means somewhere you need a background job that notices “this hold expired, flip the slot back to available” — and you need the UI to show the right thing while the clock is ticking.

A hold is to a sold-out slot what limited is to available: a derivation, not a state. You can compute it from the reservations table by status. But the derivation has to happen before you tell a new customer the day is free.

What the admin side has to do that the customer side doesn’t

The customer calendar is one piece of information per square: can you book this day or not? The operator calendar is six:

Feature StateWho caused itMoney?In revenue?ReversibleEmits ledger
available nobody no — — —
limited one+ customer yes yes if slot cancels already did
sold_out enough customers yes yes with refunds already did
hold checkout-in-progress pending no, not yet auto-expires not yet
blocked operator no no delete anytime never

Same grey square on the customer calendar, five completely different stories underneath.

If the UI only exposes “closed,” the operator ends up ghosting their customers (block it with a fake reservation) or grinding (manually refund the fake). If the UI exposes the real taxonomy, the operator gets a one-click button for the actual thing that happened, and the ledger never sees a lie.

The product lesson

The pattern here isn’t specific to tour operators. Anywhere you have a resource and a schedule — a photographer’s hours, a therapist’s chair, a conference room, a classroom, a rental — there is going to be a “closed” surface that hides multiple underlying states. The temptation is always to collapse them into one, because the customer only sees one.

Don’t. The customer sees one. The system has to see all of them, because each one owes different people different things.

The customer-facing calendar is a view. The admin calendar is the truth. If your “truth” table is actually five different kinds of row pretending to be the same kind of row, you will eventually lie to a bank, an accountant, or a paying customer. One of those three will notice, and the fix will be much more expensive than the extra column in the schema would have cost you up front.

Four grey squares. Four stories. Only one of them is a calendar event.