You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Optionally show booked times as greyed out on the booking page #19
Optionally show times that are already booked as greyed-out, non-clickable entries in the slot list, instead of omitting them. For public-hours use cases (intro calls, clinics, tutoring) a visibly busy calendar communicates demand and limited availability; today an empty-looking list reads as "nothing here".
Per event type, default off. Reasoning is in the discussion: the endpoint is public and unauthenticated, and while the host's schedule is already inferable by comparing days or by polling and diffing, greying makes that effortless and exact for a casual visitor. Fine when hours are public, not fine for an instance fronting internal team calendars.
Scope it narrowly
Grey only slots blocked by a booking or a calendar conflict, inside the host's offered hours.
Do not render:
times outside the host's availability rules (that's the whole rest of the day, and it's the part that leaks the frame)
times unavailable only because of min_notice_minutes - greying those claims "someone booked this" when nobody did, corrupting exactly the signal this feature exists to send. Those get the hint in Booking page: explain empty slot lists and minimum-notice gaps #20 instead.
Buffer-blocked slots are a judgement call; leaning toward greying them, since they genuinely are unbookable.
Implementation notes
slots.Generate (internal/slots/generate.go) never materialises unavailable starts. It subtracts busy from the working windows and then walks what remains, so the skipped starts don't exist as objects. It has to emit them, tagged with a reason, for this to be possible at all.
Do not surface unavailable slots through MCP get_available_slots or the booking assistant. The assistant's invariant is that it only ever sees computed availability; hand it a list containing unbookable entries and it will eventually offer one.
Latent bug to avoid:book.html derives which calendar dates are clickable from Object.keys(slotsByDay). Mix unavailable slots into that structure and a fully booked day starts looking bookable. The available-dates set must count bookable slots only.
Watch payload size on the month prefetch.
Three surfaces: book.html, manage.html, embed.js. Markup and JS are not shared; styling is (booking.css). Verify desktop and mobile on each.
Engine and API are on dev; the three booking surfaces are still to do.
Engine (b172eac). GenerateWithTaken sits alongside Generate, sharing the same walk. "Taken" is defined by difference rather than by inspecting reasons: a start is taken if it would have been offered with no busy intervals at all, and is not offered with the real ones.
That definition turned out to be doing real work. It is correct for every routing mode without special-casing any of them, and it makes three mislabellings structurally impossible rather than merely untested - a start outside the host's working hours, one withheld by minimum notice, and one lost to a host pool that cannot satisfy the routing mode are all absent from both passes, so they cancel. Each has a test, because greying a slot asserts "somebody booked this" and saying that about a time the host does not work would both mislead the booker and expose the shape of the working day.
Buffers do count as taken, deliberately. Taken slots carry no host ids: the page needs the time, and naming who is busy discloses more about an individual than greying requires.
API (6d8174e). show_taken_slots per event type, migration 00057, default off. computeSlots gained an explicit includeTaken parameter so all three call sites have to state intent; the MCP tool and the booking assistant pass false and there is a test at the MCP surface, with the event type opted in, asserting no taken field reaches an agent.
One bug the tests caught first: taken was present even when not opted in, because converting an empty result yields an empty non-nil slice. The result now carries an explicit flag, which also keeps "opted in, nothing booked today" distinct from "does not show taken times".
Next: book.html, manage.html and embed.js, plus the editor toggle.
Enabled per event type under Visibility, off by default. GET /v1/event-types/{slug}/slots returns a taken array for opted-in event types and omits the field entirely otherwise, so a client can tell "does not show taken times" from "opted in, nothing booked today".
The three scoping decisions in the issue body all held:
Only starts removed by a booking or calendar conflict are shown. GenerateWithTaken computes that as the difference between a normal pass and one ignoring busy, which makes out-of-hours, min-notice and routing-mode gaps structurally incapable of being labelled as booked rather than merely untested.
Agents never see taken slots. computeSlots takes an explicit includeTaken so all three call sites state intent, and there is a test at the MCP surface, with the event type opted in, asserting no taken field reaches an agent.
The available-dates trap the issue called out is handled by bookableDayKeys in the shared booking-logic.js: grouping taken slots by day means a fully booked day still produces a key, so the calendar can't derive clickable dates from those keys. Fully booked days are still openable on purpose - a struck-through list explains itself better than a dead date.
Verified in a real browser on all three surfaces plus mobile, with the editor toggle round-tripped both ways.
Activity
pullfrog commentedon Sep 1, 2026
shockalotti commentedon Sep 2, 2026
Engine and API are on
dev; the three booking surfaces are still to do.Engine (b172eac).
GenerateWithTakensits alongsideGenerate, sharing the same walk. "Taken" is defined by difference rather than by inspecting reasons: a start is taken if it would have been offered with no busy intervals at all, and is not offered with the real ones.That definition turned out to be doing real work. It is correct for every routing mode without special-casing any of them, and it makes three mislabellings structurally impossible rather than merely untested - a start outside the host's working hours, one withheld by minimum notice, and one lost to a host pool that cannot satisfy the routing mode are all absent from both passes, so they cancel. Each has a test, because greying a slot asserts "somebody booked this" and saying that about a time the host does not work would both mislead the booker and expose the shape of the working day.
Buffers do count as taken, deliberately. Taken slots carry no host ids: the page needs the time, and naming who is busy discloses more about an individual than greying requires.
API (6d8174e).
show_taken_slotsper event type, migration 00057, default off.computeSlotsgained an explicitincludeTakenparameter so all three call sites have to state intent; the MCP tool and the booking assistant pass false and there is a test at the MCP surface, with the event type opted in, asserting notakenfield reaches an agent.One bug the tests caught first:
takenwas present even when not opted in, because converting an empty result yields an empty non-nil slice. The result now carries an explicit flag, which also keeps "opted in, nothing booked today" distinct from "does not show taken times".Next: book.html, manage.html and embed.js, plus the editor toggle.
feat(slots): report the starts a booking took away (#19, engine)
feat(slots): per-event-type opt-in and API for taken slots (#19, part 2)
feat(booking): render taken slots on the three booking surfaces (#19,…
docs: taken slots (#19)
shockalotti commentedon Sep 3, 2026
Shipped in 0.8.0, across b172eac (engine), 6d8174e (schema + API), 7370179 (three surfaces + editor) and be500e4 (docs).
Enabled per event type under Visibility, off by default.
GET /v1/event-types/{slug}/slotsreturns atakenarray for opted-in event types and omits the field entirely otherwise, so a client can tell "does not show taken times" from "opted in, nothing booked today".The three scoping decisions in the issue body all held:
GenerateWithTakencomputes that as the difference between a normal pass and one ignoring busy, which makes out-of-hours, min-notice and routing-mode gaps structurally incapable of being labelled as booked rather than merely untested.computeSlotstakes an explicitincludeTakenso all three call sites state intent, and there is a test at the MCP surface, with the event type opted in, asserting notakenfield reaches an agent.The available-dates trap the issue called out is handled by
bookableDayKeysin the sharedbooking-logic.js: grouping taken slots by day means a fully booked day still produces a key, so the calendar can't derive clickable dates from those keys. Fully booked days are still openable on purpose - a struck-through list explains itself better than a dead date.Verified in a real browser on all three surfaces plus mobile, with the editor toggle round-tripped both ways.
feat(event-types): rename the booking link until the first booking