CU-08: AI booking chatbot (the differentiator) on the public booking page

Priority: P1 Accounts/sessions: Anonymous invitee (no login). Host P1 (ravikantguptaofficial@gmail.com) must already own a dedicated event type and have a default availability schedule. Gmail for P1 must be logged in (mail.google.com) and Google Calendar (calendar.google.com) for L3. Invitees use plus-aliases of P1's Gmail. Parallel-safe: Yes — this suite books slots on a dedicated, RUNID-scoped event type and never edits global host availability. Exclusive (rewrites global host availability?): No. Estimated time: 30 minutes. L3 reality checks: Confirmation email lands in P1 Gmail (search by RUNID), and a real Google Calendar event is created at the booked time.

Goal

This suite proves Calendo's headline differentiator: an invitee can book a meeting by talking to an AI in plain English on the public booking page instead of hunting through a grid of time slots. The AI must reply fast (~2s), propose only real available slots that match the host's actual availability (never hallucinated), handle ambiguous and impossible requests gracefully, complete a real booking conversationally, and coexist with the traditional grid (the grid must remain a working fallback, and the AI must never offer a slot the grid does not). A working conversational booking flow is the single most important thing that distinguishes Calendo from Calendly, so this is a top-priority production check.

CRITICAL GROUNDING FINDING — read before you start. In the current production code (/Users/ravf/projects/calendo/public/booking/index.html), the inline AI chat panel (#ai-chat-prompt) is hardcoded style="display:none" with the comment <!-- AI Chat inline (hidden for now) -->, and the floating chat bubble component (/Users/ravf/projects/calendo/src/chat-widget.js, #chatToggle) is NOT imported into the booking page. Nothing in the page JS ever reveals the chat. So on production today, an ordinary invitee sees NO AI chat on the booking page. The conversational backend (POST /api/ai/chat) is fully wired and live. Therefore this runbook has TWO parts: (A) verify whether any AI chat surface is visible to a real invitee (expected: NOT visible — this is itself a P1 finding to FLAG), and (B) force-reveal the inline panel via the browser console so you can still exercise and verify the conversational booking capability end to end. Tag every console-reveal step clearly; the human reviewer must know the capability was tested through a workaround, not through the shipped UX.

Preconditions

Test data

Steps

Part 0 — Set up the dedicated event type (host dashboard)

  1. Action: Go to the dashboard (https://calendo.dev/dashboard/). Wait for the app shell (#appLayout) and welcome header (#welcomeHeader) to render. -> Expect: Dashboard loads, sidebar visible with tabs (.sidebar-nav a[data-tab="..."]). If an onboarding wizard (#calendarOnboarding) appears, dismiss/skip it. -> [L1]
  1. Action: On the Overview, click the "+ New" event-type button (#overviewCreateEtBtn). In the modal (#editEtModal), set name = CU08 AI Chat <RUNID> (#editEtName), duration = 30 (#editEtDuration), description = CU-08 runbook event (#editEtDesc). Click the save/create button (#editEtSaveBtn). -> Expect: Modal closes; the new event appears in the overview event-type list (#overviewEventTypes contains CU08 AI Chat <RUNID>). -> [L1]
  1. Action: Read the dashboard booking link (#bookingLinkValue) and record <HOST_SLUG> (text after user=). Confirm the new event's slug (open the event, or derive cu08-ai-chat-<RUNID>). -> Expect: You have a working booking URL https://calendo.dev/booking/?user=<HOST_SLUG>&event=cu08-ai-chat-<RUNID>. -> [L2] Capture screenshot: cu08-eventtype-created.

Part A — Does a real invitee see the AI chat? (expected: NO — flag it)

  1. Action: Open a fresh/incognito browser context (anonymous invitee, no Calendo cookies). Navigate to the booking page: https://calendo.dev/booking/?user=<HOST_SLUG> (event-list view). Wait for the host header and event list (#event-types-grid, .event-card) to render. -> Expect: The booking page renders the host name and a "Select a meeting type" list containing CU08 AI Chat <RUNID>. -> [L1]
  1. Action: Look for any AI chat affordance: a floating "Chat with AI" bubble in the bottom-right (#chatToggle from chat-widget.js), or an inline "Ask AI" panel with an input (#ai-chat-prompt / #ai-chat-inline-input / #ai-chat-inline-send). Check both the event-list view and after selecting the event (#booking-view). -> Expect (per grounding): NO visible AI chat element. #ai-chat-prompt is present in the DOM but display:none; no floating bubble exists. -> [L1]
    • If you DO see a visible chat UI: great — production has been updated since grounding. Record exactly what you see (selector, location) and use it for Part B instead of the console reveal. Note "AI chat visible to invitee = YES" in the report.
    • If you do NOT see a chat UI (expected): record FINDING-A: "AI booking chat is not exposed to invitees in production — the differentiator is dark." This is a P1 product gap, not a script error. Proceed to Part B to verify the backend capability still works.

Part B — Reveal the inline chat and verify conversational availability (workaround)

Steps 6-15 use a console workaround to un-hide #ai-chat-prompt. This exercises the SAME code path real invitees would use once the panel is shown. Tag the whole part as [workaround] in the report.

  1. Action: Still on https://calendo.dev/booking/?user=<HOST_SLUG> (event-list view, where #ai-chat-prompt lives), open the browser devtools console and run: document.getElementById('ai-chat-prompt').style.display='block'; document.getElementById('ai-chat-prompt').scrollIntoView(); -> Expect: A panel appears with the helper text "Not sure which meeting to pick?...", a text input (#ai-chat-inline-input, placeholder e.g. "I need a 30 min call next Tuesday afternoon"), and an "Ask AI" button (#ai-chat-inline-send). -> [L1] [workaround] Capture screenshot: cu08-chat-revealed.
  1. Action: Type a concrete natural request scoped to a real available day. First check the grid (open the event, note which weekdays show .calendar-day.available). Then in the chat input type, e.g., I need 30 minutes on <a known-available weekday> afternoon (e.g. "this Thursday afternoon"). Click "Ask AI" (#ai-chat-inline-send) or press Enter. Start a stopwatch when you send. -> Expect: A "..." typing bubble appears, then within ~2-4 seconds (allow up to ~6s for cold Claude calls) an assistant reply that proposes one or more specific times in 12-hour format ("3:00 PM"), in the host timezone, that are real. Record the reply latency. -> [L1]
    • Note on rate limits: If the reply is a static fallback like "I can help you book a meeting...! They offer: ... What kind of meeting do you need?" (the event-type list with no times), the Claude API was rate-limited/overloaded (429/529) and fell back. This is an EXTERNAL dependency state, NOT a Calendo bug — record it as RATE-LIMITED, wait 60s, and retry the same message up to 3 times. If it never returns real times after retries, mark the conversational checks BLOCKED-EXTERNAL and still complete the grid path (Part D).
  1. Action: Cross-check every time the bot proposed against the grid. Open the event in a second tab/view (#booking-view), pick the same date, and read the .time-slot buttons. -> Expect: Every time the AI offered appears as a real .time-slot for that date on the grid. The AI must NOT offer a time absent from the grid. -> [L2] Capture screenshot: cu08-grid-matches-chat (grid time slots for the proposed date).
  1. Action: Pick a proposed slot conversationally — reply in the chat with something like Yes, 3:00 PM works, book it (use one of the exact times the bot offered). -> Expect: The bot replies with confirmation language ("Great choice! I'll pull up a quick form...") AND an inline mini-form appears in the chat (#ai-book-time dropdown pre-filled with real slot options, #ai-book-name, #ai-book-email, #ai-book-confirm). The slots in #ai-book-time come directly from the backend's computed real slots. -> [L1]
    • If no form appears: the backend only shows the form when the reply text contains confirmation phrases AND real slots exist. Try a clearer pick ("book the 3pm slot"). If still no form after a clear pick with known availability, record FINDING-B: conversational confirm did not surface the booking form.
  1. Action: In the inline form, select the intended time in #ai-book-time, enter name CU08 Invitee <RUNID> (#ai-book-name) and email ravikantguptaofficial+inv-<RUNID>@gmail.com (#ai-book-email), then click "Confirm Booking" (#ai-book-confirm). -> Expect: The form replaces itself with a green "✓ Booking confirmed! Check your email for details." and the bot adds a message "Your CU08 AI Chat <RUNID> is booked! A calendar invite has been sent to ravikantguptaofficial+inv-<RUNID>@gmail.com." -> [L1]
    • Record the booked date/time (host timezone) for L3 — call it <INV_SLOT>.
  1. Action (persistence): In a host-authenticated tab, go to the dashboard Bookings tab (.sidebar-nav a[data-tab="bookings"], panel #panel-bookings). Look for the new booking under invitee CU08 Invitee <RUNID> at <INV_SLOT>. -> Expect: The booking from the AI chat appears in the host's bookings list with the correct invitee name, email, event type, and time. -> [L2] Capture screenshot: cu08-dashboard-booking-from-chat.

Part C — Ambiguity and impossible-request handling

  1. Action: In the chat (re-reveal #ai-chat-prompt if you navigated away; the conversation session persists via localStorage key calendo_chat_<HOST_SLUG>), send an ambiguous request: Can we meet next Tuesday? -> Expect: The bot either (a) clarifies (e.g., asks morning vs afternoon, or confirms which Tuesday) or (b) sensibly proposes real available times on the next upcoming Tuesday in 12-hour format. It must NOT invent times outside availability. -> [L1] Capture screenshot: cu08-chat-ambiguous.
  1. Action: Send an impossible request outside availability: Book me Sunday at 3am (or any day/time the grid shows as fully unavailable — verify against the grid first). -> Expect: The bot handles it gracefully: it says that time/day is not available and offers the next real available slot(s) instead. It must NOT hallucinate a slot, must NOT confirm a 3am booking, and must NOT produce a booking form for an unavailable time. -> [L1] Capture screenshot: cu08-chat-impossible.
  1. Action: Send an off-topic message to confirm scope enforcement carries over to the public chat: What's the weather in San Francisco? -> Expect: The bot declines and redirects to scheduling (consistent with the dashboard AI's scope enforcement). It does not answer the weather. -> [L1] Capture screenshot: cu08-chat-scope.

Part D — Grid fallback coexistence (the traditional path still works)

  1. Action: As the anonymous invitee, navigate fresh to https://calendo.dev/booking/?user=<HOST_SLUG>&event=cu08-ai-chat-<RUNID> (no console tricks). Wait for #booking-view. Pick the first available day (.calendar-day.available), then the first .time-slot. Fill #input-name = CU08 Grid <RUNID>, #input-email = ravikantguptaofficial+grid-<RUNID>@gmail.com. Click "Confirm Booking" (#btn-confirm). -> Expect: Confirmation screen with title "Booking Confirmed!" (.confirmation-title), and manage links in #confirmation-manage ("Reschedule" -> /booking/reschedule.html?token=..., "Cancel booking" -> /booking/cancel.html?token=...). Record the booked date/time as <GRID_SLOT> and the cancel/reschedule hrefs. -> [L1] [L2] Capture screenshot: cu08-grid-confirmed.
  1. Action (coexistence assertion): Confirm both paths produced bookings and that the AI never offered a slot the grid lacked. Cross-reference: the AI-offered times (Step 7-9) ⊆ grid .time-slot set (Step 8); both <INV_SLOT> and <GRID_SLOT> exist as host bookings (Step 11 + Bookings tab). -> Expect: AI path and grid path coexist; AI slots are a subset of grid slots; two distinct bookings exist (one per RUNID alias). -> [L2]

L3 reality checks

Email (Gmail, P1 inbox):

  1. Open https://mail.google.com (P1 logged in). Search: inv-<RUNID>. -> Expect: A booking-confirmation email addressed to / referencing ravikantguptaofficial+inv-<RUNID>@gmail.com for the AI-chat booking, subject referencing the event (CU08 AI Chat <RUNID>) and time <INV_SLOT>, body containing the meeting details and Reschedule/Cancel links pointing to calendo.dev/booking/reschedule.html?token= and .../cancel.html?token=. Capture screenshot: cu08-L3-email-chat.
  2. Search: grid-<RUNID>. -> Expect: A second confirmation email for the grid booking at <GRID_SLOT>. Capture screenshot: cu08-L3-email-grid.
    • If the host also receives a separate "new booking" notification in this same inbox, confirm it too; note its presence.

Calendar (Google Calendar, P1):

  1. Open https://calendar.google.com (P1 logged in). Navigate to the date of <INV_SLOT>. -> Expect: A real event at exactly <INV_SLOT> (host timezone) titled with the event name (e.g. CU08 AI Chat <RUNID> with <host>), with the invitee ravikantguptaofficial+inv-<RUNID>@gmail.com as an attendee. Capture screenshot: cu08-L3-gcal-chat.
  2. Navigate to the date of <GRID_SLOT>. -> Expect: A real event at <GRID_SLOT> for the grid booking with attendee ...+grid-<RUNID>@gmail.com. Capture screenshot: cu08-L3-gcal-grid.
    • If P1's Google Calendar is not connected to Calendo, mark GCal L3 "cannot verify — calendar not connected" and rely on Calendo persistence (Step 11) plus email L3.

Cleanup

Leave the host accounts and calendars clean for the next run. Do all of the following:

  1. Cancel the AI-chat booking: open the Reschedule/Cancel link from the inv-<RUNID> confirmation email (or /booking/cancel.html?token=<token> recorded earlier), confirm cancellation. -> Expect cancel confirmation page.
  2. Cancel the grid booking: use the cancel link from <GRID_SLOT> confirmation (Step 15 href). Confirm cancellation.
  3. Verify both are cancelled in the dashboard Bookings tab (status = cancelled or removed from upcoming).
  4. Delete the dedicated event type CU08 AI Chat <RUNID> from the dashboard (Event Types tab -> the event -> delete; confirm). This also removes it from the booking page.
  5. Delete the test calendar events if they were not removed automatically by the cancellation: in Google Calendar, delete the <INV_SLOT> and <GRID_SLOT> events titled with <RUNID> if still present.
  6. Optional Gmail tidy: the plus-alias confirmation/cancellation emails can be left (they are searchable and harmless) or archived. Do not delete other mail.
  7. Reset the console reveal: none needed — closing/refreshing the incognito context discards the display:block override; no persistent change was made to production.

Pass/Fail criteria

The run PASSES only if ALL of the following hold (treat external rate-limit as a documented non-failure, see notes):

The run FAILS if: the AI proposes a time NOT on the grid (hallucination), confirms an impossible/out-of-availability time, the booking form completes but no booking persists, the grid fallback is broken, or the confirmation email/GCal event never appears while the booking shows confirmed in Calendo. A Claude 429/529 rate-limit fallback is recorded as BLOCKED-EXTERNAL, not FAIL, but the suite cannot PASS the conversational criteria until a non-rate-limited run is achieved.

Evidence to capture

Manual residue / cannot-verify