CU-19: Embeddable booking widget (inline / popup / badge)

Priority: P3 Accounts/sessions: P1 host (ravikantguptaofficial@gmail.com) logged into Calendo dashboard; an external/data-URI test page that the agent constructs in-browser. No host-email login is required to render the widgets; an email check is optional (L3) and uses a P1 plus-alias invitee. Parallel-safe: true (does not touch global availability; only reads the existing booking page and creates at most one disposable test booking) Exclusive (rewrites global host availability?): No Estimated time: 25 minutes L3 reality checks: None required. One OPTIONAL L3 email check (the confirmation email for the booking completed through the embedded iframe) is described and may be skipped without failing the suite.

Goal

This suite proves that a Calendo host can drop a single snippet onto a completely unrelated third-party website and have a working booking experience appear there, in three forms: an inline scheduler (full booking page rendered in an iframe in the page body), a popup button (a button that opens the booking page in a modal overlay), and a floating badge (a fixed bottom-right "Book a meeting" pill that opens the same modal). It further proves the cross-origin plumbing works: the embed scripts are served with Access-Control-Allow-Origin: *, the booking page is iframable (no X-Frame-Options: DENY), the embed=1 chrome-stripping flag is applied, a real booking can be completed inside the embedded iframe with the confirmation rendering inside the embed, and the booking page fires a calendo:booking-confirmed postMessage to the parent window that the embed script re-dispatches as a DOM event. This matters because the embed widget is how Calendo reaches invitees who never visit calendo.dev directly — it is the product's distribution surface on customer websites.

Preconditions

Test data

Steps

Order rationale: Steps 1-7 generate and capture the three real snippets from the dashboard (source of truth for what a customer would paste). Steps 8-12 build the external host and verify each render mode in isolation. Steps 13-17 complete a real booking through the embedded iframe and verify in-embed confirmation + the postMessage event. Steps 18-19 verify the cross-origin transport headers. Doing snippet capture first guarantees the host page uses the app's actual generated code, not invented markup.

  1. Action: Go to https://calendo.dev/dashboard/ and confirm you are logged in (sidebar visible, #welcomeHeader present). On the Overview tab, locate the booking link field (#bookingLinkValue). Read its text. Expect: It reads https://calendo.dev/booking/?user=<something>. Record the part after user= as HOST_SLUG. [L2]
    • Capture screenshot: cu19-01-dashboard-overview-bookinglink
  1. Action: Open the Settings tab (left sidebar link with data-tab="settings"), then click the Branding settings sub-tab (button labeled "Branding", which calls switchSettingsTab(this,'branding'); panel #settingsTabBranding). Scroll to the card titled "Embed on Your Website" (.settings-card-title text "Embed on Your Website"). Expect: The card shows three toggle buttons — Inline (.embedTabBtn[data-embed="inline"], active by default), Popup Button (.embedTabBtn[data-embed="popup"]), Floating Badge (.embedTabBtn[data-embed="badge"]) — a code block (#embedCodeBlock), a Copy Code button (#copyEmbedBtn), and a live Preview (#embedPreview). [L1]
    • Capture screenshot: cu19-02-embed-card-inline-default
  1. Action: With Inline selected, read the code in #embedCodeBlock. Expect: It is exactly two lines of the form:
    • Capture screenshot: cu19-03-inline-snippet-and-preview
  1. Action: Click the Popup Button toggle (.embedTabBtn[data-embed="popup"]). Read #embedCodeBlock. Expect: Snippet becomes:
    • Capture screenshot: cu19-04-popup-snippet-and-preview
  1. Action: Click the Floating Badge toggle (.embedTabBtn[data-embed="badge"]). Read #embedCodeBlock. Expect: Snippet becomes a single line:
    • Capture screenshot: cu19-05-badge-snippet-and-preview
  1. Action: Click Copy Code (#copyEmbedBtn). Expect: The button gives visual feedback that the code was copied (e.g. a toast or a temporary "Copied" state). This confirms the copy affordance works; treat the recorded snippet text from steps 3-5 as the source of truth for the host page regardless of clipboard access. [L1]
  1. Action: Sanity-check the three snippet script URLs resolve. In the browser, open in turn: https://calendo.dev/embed/calendo-embed.js and https://calendo.dev/embed.js . Expect: Each returns JavaScript (not a 404 / HTML error). calendo-embed.js contains the strings data-mode, calendo-embed-overlay, and calendo:booking-confirmed; embed.js contains data-calendo-user and calendo-badge. [L2]
    • Capture screenshot: cu19-07-embed-scripts-load
  1. Action: Build the external host page. Create an HTML document (via data:text/html URI or a scratch page the agent can author) with <title>Embed Host <RUNID></title> and a body that contains, in this order: a heading "Embed Host <RUNID>", then SNIPPET_INLINE, then a horizontal rule, then SNIPPET_POPUP, then a horizontal rule, then SNIPPET_BADGE. IMPORTANT: this page's origin must NOT be calendo.dev (that is the whole point of the cross-origin test). Load this page in the browser. Expect: The page loads with the heading visible and the browser does not block the calendo.dev scripts. [L1]
    • Capture screenshot: cu19-08-host-page-loaded
  1. Action: On the host page, locate the inline embed region (the <div id="calendo-inline">). Expect: The embed script has replaced/filled it with a bordered card (.calendo-embed-inline) containing an <iframe title="Calendo Booking"> whose src is https://calendo.dev/booking/<HOST_SLUG>?embed=1. The iframe renders the booking page (host name, event type list, or calendar) in chrome-stripped form (white background, tight padding from embed=1). [L1]
    • Capture screenshot: cu19-09-inline-iframe-rendered
  1. Action: Locate the popup embed (<div id="calendo-popup">). Expect: The script rendered a button .calendo-embed-popup-btn reading "Book a Meeting" with a calendar icon. No iframe is visible yet (popup is lazy). [L1]
  1. Action: Click the "Book a Meeting" popup button. Expect: A full-screen overlay appears (.calendo-embed-overlay) with a centered modal (.calendo-embed-modal) containing a close "×" button (.calendo-embed-modal-close) and an iframe whose src is https://calendo.dev/booking/<HOST_SLUG>?embed=1 showing the booking page. [L1]
    • Capture screenshot: cu19-11-popup-modal-open
    • Action: Press Escape (or click the "×" / the dark area outside the modal). Expect: The overlay closes and is removed from the page. [L1]
  1. Action: Look at the bottom-right corner of the host page for the floating badge (.calendo-badge, injected by /embed.js). Expect: A fixed pill-shaped blue "Book a meeting" badge with a calendar icon is pinned to the bottom-right (position: fixed; bottom/right 24px). [L1]
    • Capture screenshot: cu19-12-floating-badge
    • Action: Click the badge. Expect: A modal overlay (.calendo-overlay > .calendo-modal with .calendo-iframe) opens showing the booking page (iframe src https://calendo.dev/booking/?user=<HOST_SLUG>&embed=1). Close it with "×" or Escape and confirm it is removed. [L1]
    • Capture screenshot: cu19-12b-badge-modal-open
  1. Action: (Begin the in-embed booking — uses the inline iframe from step 9 so the confirmation is clearly observed in the page body.) Inside the inline iframe, if an event type list is shown, click the first event card (.event-card, name in .event-card-name). If data-calendo-event had pinned an event you would skip straight to the calendar; here the inline snippet has no event pin, so expect the event list first. Expect: The view advances to the date/time picker (calendar grid with .calendar-day cells). [L1]
  1. Action: In the embedded calendar, click an available day (.calendar-day.available). Then in the time slots panel (#time-slots-panel, slots are .time-slot) click the first available slot. Expect: The time slots panel becomes visible after picking a day; after clicking a slot the confirm form (#confirm-view) appears with #input-name and #input-email fields and a #btn-confirm button. If there are NO available days, abort this booking sub-flow, mark it blocked (precondition shortfall), and continue to step 18. [L1]
    • Capture screenshot: cu19-14-embedded-confirm-form
  1. Action: In the embedded confirm form, fill #input-name with Embed Test <RUNID> and #input-email with ravikantguptaofficial+inv-<RUNID>@gmail.com. Click Confirm Booking (#btn-confirm). Expect: No validation error; the button shows a pending/confirming state. [L1]
  1. Action: Wait for the booking to complete. Expect: The confirmation view (#confirmation-success-view) renders INSIDE the embedded iframe (a success heading and #confirmation-details showing the event/date/time). The confirmation appears within the embed, not in a new tab or the top-level page. [L1]
    • Capture screenshot: cu19-16-in-embed-confirmation
  1. Action: Verify the postMessage cross-frame event fired. The booking page calls window.parent.postMessage({type:'calendo:booking-confirmed', ...}, '*') when inside an iframe, and calendo-embed.js re-dispatches it on the host window as a calendo:booking-confirmed CustomEvent. To observe this, BEFORE step 13 (or by reloading the host and re-booking) the agent should have a listener registered on the host page, e.g. inject window.addEventListener('calendo:booking-confirmed', e => { window.__calendoEvt = e.detail; }); into the host page. After step 16, read window.__calendoEvt. Expect: window.__calendoEvt is a non-null object with type === 'calendo:booking-confirmed' and a bookingId (and likely event/date/time). If the agent cannot inject/inspect JS on the host page, record this as Manual residue (the in-embed confirmation in step 16 is the primary evidence the booking succeeded). [L1][L2]
    • Capture note: cu19-17-postmessage-detail (the captured event detail object, or "could not inspect — manual residue")
  1. Action: Verify the booking iframe is allowed to frame (X-Frame). Confirm steps 9/11/12 already proved the booking page rendered inside an iframe on a non-calendo.dev origin — that is the functional proof there is no X-Frame-Options: DENY and no frame-blocking CSP. As corroboration, if the agent can read network response headers, inspect the response headers for https://calendo.dev/booking/?user=<HOST_SLUG>&embed=1. Expect: X-Frame-Options is absent or SAMEORIGIN, never DENY; no Content-Security-Policy: frame-ancestors 'none'. [L2]
  1. Action: Verify CORS on the embed scripts. If the agent can read response headers, inspect the response headers for https://calendo.dev/embed/calendo-embed.js. Expect: Access-Control-Allow-Origin: *, Content-Type: application/javascript; charset=utf-8, and Cache-Control: public, max-age=3600. If headers cannot be inspected, the fact that the script executed from the non-calendo.dev host page (steps 9-12) is acceptable functional evidence — note it. [L2]
    • Capture screenshot: cu19-19-embed-script-headers (or note "headers not inspectable; functional cross-origin load confirmed")

L3 reality checks

None required for a PASS. Optional (only if step 13-16 completed a real booking and time permits):

Cleanup

The only durable artifact this suite creates is the one test booking from steps 13-16 (the snippets, host page, and modals are ephemeral). Remove it so host calendars/inboxes stay clean:

  1. Go to https://calendo.dev/dashboard/ , open the Bookings tab (data-tab="bookings"). Find the booking with invitee name Embed Test <RUNID> / email ravikantguptaofficial+inv-<RUNID>@gmail.com.
  2. Cancel it (open the booking, use the cancel action). Confirm it moves to Cancelled / disappears from upcoming. [L2]
  3. If the optional L3 booking created a Google Calendar event (only if P1's Google Calendar is connected), open calendar.google.com as P1, find the event at the booked date/time titled with the event-type name, and confirm cancellation removed it (or delete it manually). Note in evidence if a stale event remained.
  4. Discard the external host page (close the tab / data: URI). No dashboard-side cleanup is needed for the embed snippets — nothing was saved server-side by viewing them.
  5. Do NOT delete the host's booking page, event types, or availability — those are shared baseline owned by other suites.

Pass/Fail criteria

The run PASSES only if ALL of the following are true:

The run FAILS if: any snippet is malformed or points at the wrong slug/script; an embed script 404s; any mode fails to render its widget on the foreign origin; the booking page refuses to frame (blank iframe due to X-Frame-Options: DENY/CSP); a completed booking's confirmation does not render inside the embed; or the test booking is left un-cancelled.

Evidence to capture

Manual residue / cannot-verify