CU-01: Core booking lifecycle (book → reschedule → cancel) with calendar & email reality

Priority: P0 (flagship end-to-end spine — run this first; if it fails, treat the deploy as broken) Accounts/sessions: P1 host (ravikantguptaofficial@gmail.com, logged into Calendo via Google, Google Calendar connected, Gmail = host inbox) + anonymous invitee (no Calendo account; invitee identity = a plus-alias of P1's Gmail) Parallel-safe: Yes — this suite never edits global host availability or settings; it only creates one event type, one booking, then reschedules and cancels that same booking. Multiple RUNIDs cannot collide. Exclusive (rewrites global host availability?): No Estimated time: ~30 minutes for one full run (including all L3 checks in Gmail and Google Calendar) L3 reality checks: Google Calendar event CREATED at booked slot (invitee = attendee) → MOVED to new slot after reschedule → REMOVED after cancel; plus 3 email pairs (confirmation, reschedule, cancellation) each landing in BOTH the invitee plus-alias inbox AND the host inbox.

Goal

This suite proves Calendo's single most important promise: an anonymous invitee can land on a host's public booking page, pick a real available time, book it, and the meeting actually materializes everywhere it should — the booking is persisted in the host dashboard, a real event appears on the host's Google Calendar with the invitee attached, and real confirmation emails arrive for both parties. It then proves the invitee can self-serve reschedule (the calendar event MOVES and a reschedule email goes out) and cancel (the calendar event is REMOVED and a cancellation email goes out), all via the tokenized links from the confirmation page — no host involvement and no login required. If any link in this chain breaks, Calendo is not shippable, so this is the highest-priority runbook and every step is granular and independently observable.

Preconditions

Test data

Steps

Phase A — Read host slug and create the dedicated event type (as P1)

  1. Action: Go to the dashboard (https://calendo.dev/dashboard/). Confirm you are on the Overview tab (sidebar link .sidebar-nav a[data-tab="overview"]; panel #panel-overview has class active). Expect: Dashboard chrome loads; #welcomeHeader is visible; #overviewEventTypes lists existing event types (e.g. contains "Minute"). [L1]
  1. Action: Read the host booking link text from #bookingLinkValue (it is the full public URL, e.g. https://calendo.dev/booking/?user=<HOST_SLUG>). Record <HOST_SLUG> = everything after user=. Expect: A non-empty URL containing /booking/?user=. Capture screenshot: cu01-01-dashboard-overview. [L2]
  1. Action: Click the "+ New" event-type button (#overviewCreateEtBtn). Expect: The event edit modal #editEtModal becomes visible (in create mode; its save button reads "Create"). [L1]
  1. Action: In the modal, fill Name (#editEtName) with CU01 Lifecycle <RUNID>; set Duration (#editEtDuration) to 30; fill Description (#editEtDesc) with CU-01 automated lifecycle test <RUNID>. Expect: Fields show the typed values. [L1]
  1. Action: Click Save/Create (#editEtSaveBtn). Wait ~2s for the overview to re-render. Expect: The modal closes and #overviewEventTypes now contains CU01 Lifecycle <RUNID>. Capture screenshot: cu01-02-event-created. [L2]

Phase B — Book the slot as an anonymous invitee

  1. Action: Open a fresh browser context/tab that is NOT logged into Calendo (to act as the anonymous invitee), and navigate to the public booking page for this event: https://calendo.dev/booking/?user=<HOST_SLUG>&event=cu01-lifecycle-<RUNID>. (If the slug differs from the derived one, use the slug shown on the dashboard event card.) Expect: The booking view #booking-view is visible; the host name appears in #host-name; the event name appears in the sidebar (#sidebar-event-name); duration shows "30 min". The step indicator #step-indicator shows "Select Time". [L1]
  1. Action: In the calendar (#calendar), find the first available day. Available days have class calendar-day available (clickable, dark text); unavailable days have calendar-day unavailable. If the current month shows no .calendar-day.available, click the forward navigation button (the last .calendar-nav-btn) and retry, up to 6 months forward. Click the FIRST available day. Expect: The time-slots panel #time-slots-panel becomes visible and shows one or more .time-slot buttons. [L1]
  1. Action: Note the exact date and the exact time label of the FIRST .time-slot (e.g. "9:00am") — record this as the BOOKED SLOT; you will verify it on Google Calendar later. Click that first .time-slot. Expect: The confirm form #confirm-view becomes visible; #confirm-datetime-text shows the selected date/time; the step indicator advances to "Your Details". Capture screenshot: cu01-03-slot-selected. [L1]
  1. Action: Fill the booking form — Name (#input-name) = CU01 Invitee <RUNID>; Email (#input-email) = ravikantguptaofficial+inv-<RUNID>@gmail.com. Leave Recurrence (#input-recurrence) as "One-time". Do not add additional guests. Expect: Both fields show the typed values; no validation error in #form-error. [L1]
  1. Action: Click "Confirm Booking" (#btn-confirm). Wait for the network call to complete. Expect: The confirmation view #confirmation-success-view appears; .confirmation-title reads exactly "Booking Confirmed!"; #confirmation-details contains the event name CU01 Lifecycle <RUNID>, "Duration 30 min", the booked Date, the booked Time range, the Timezone, and the Host name. [L1]
  1. Action: On the confirmation page, locate the manage links block #confirmation-manage. Expect: It contains a "Reschedule" link whose href matches /booking/reschedule.html?token=... and a "Cancel booking" link whose href matches /booking/cancel.html?token=.... Both share the SAME token. RECORD this token string (the token= value) — you will reuse the reschedule and cancel URLs in later phases even though they are also on-screen. Also confirm the add-to-calendar buttons (#calendar-links → "Google Calendar", "Outlook", "Download .ics") are present. Capture screenshot: cu01-04-booking-confirmed. [L1]

Phase C — Persistence check in the host dashboard

  1. Action: Switch back to the P1 dashboard tab (https://calendo.dev/dashboard/). Click the Bookings sidebar tab (.sidebar-nav a[data-tab="bookings"]). Wait ~1s for the table to load. Expect: #panel-bookings is active; the "Upcoming" filter button (.filter-btn[data-filter="upcoming"]) is active by default. [L1]
  1. Action: In the bookings table (#bookingsTableContainer), find the row for this booking. Expect: A row shows invitee name CU01 Invitee <RUNID>, event name CU01 Lifecycle <RUNID>, the booked date/time matching the slot from step 8, and a confirmed badge (.badge-confirmed). The row has a "Cancel" action button. Capture screenshot: cu01-05-dashboard-booking-row. [L2]
  1. Action: Reload the dashboard (full page refresh) and return to the Bookings tab → Upcoming filter. Expect: The same booking row is still present after reload (proves server-side persistence, not just in-memory UI state). [L2]

Phase D — L3: confirm the real Google Calendar event + confirmation emails

  1. Action (L3 calendar): Switch to the Google Calendar tab (https://calendar.google.com), logged in as P1. Navigate to the exact BOOKED date from step 8 (use the calendar's date navigation; switch to Day or Week view on that date). Expect: An event exists at the booked start time spanning 30 minutes; its title corresponds to the Calendo meeting (typically CU01 Lifecycle <RUNID> or "<event> with <host>"); opening the event shows the invitee ravikantguptaofficial+inv-<RUNID>@gmail.com listed as a guest/attendee. Capture screenshot: cu01-06-gcal-created. [L3]
  1. Action (L3 invitee email): Switch to the Gmail tab (https://mail.google.com). In the search box, search exactly: inv-<RUNID> (this matches the plus-alias in the To: field). Expect: A booking-confirmation email addressed to ravikantguptaofficial+inv-<RUNID>@gmail.com is present, with a subject indicating a confirmed booking for CU01 Lifecycle <RUNID>, the correct date/time in the body, and working Reschedule and Cancel links. Capture screenshot: cu01-07-email-invitee-confirm. [L3]
  1. Action (L3 host email): In Gmail, search exactly: CU01 Lifecycle <RUNID> (and/or subject:(new booking)). Expect: A host-notification email in the P1 inbox announcing the new booking from CU01 Invitee <RUNID> at the booked time. (Note: because invitee = plus-alias of the host inbox, both the invitee confirmation and the host notification land in this one inbox — confirm BOTH distinct messages exist.) Capture screenshot: cu01-08-email-host-confirm. [L3]

Phase E — Reschedule via the invitee link

  1. Action: In the invitee (anonymous) browser context, navigate to the reschedule URL: https://calendo.dev/booking/reschedule.html?token=<TOKEN> (the token recorded in step 11; you may also click the "Reschedule" link directly from the still-open confirmation page). Expect: The reschedule UI #reschedule-ui is visible; #current-booking-info shows the CURRENT booking — #current-event contains CU01 Lifecycle <RUNID> and #current-datetime shows the originally-booked slot; a calendar #calendar is shown for picking a new time. [L1]
  1. Action: In the reschedule calendar, find the first available day that yields a slot DIFFERENT from the original booked slot (navigate forward with the last .calendar-nav-btn if needed). Click an available day, then click a .time-slot that is NOT the original time. Record this as the NEW SLOT (date + time). Expect: The confirm view #confirm-view appears; #confirm-datetime shows the new time range and date; #confirm-event shows the event name. Capture screenshot: cu01-09-reschedule-newtime-selected. [L1]
  1. Action: Click "Confirm Reschedule" (#btn-reschedule). Wait for the request to finish. Expect: The success state #success-state appears; .confirmation-title reads "Booking Rescheduled"; #confirmation-details shows the NEW date/time, the invitee name, and the invitee email. Capture screenshot: cu01-10-reschedule-success. [L1]
  1. Action: Back on the P1 dashboard, go to Bookings → Upcoming and reload if needed. Expect: The booking row for CU01 Invitee <RUNID> now shows the NEW date/time (from step 19), still with a confirmed badge. The OLD time no longer appears for this invitee. [L2]
  1. Action (verify the old token state): In the invitee context, re-open the ORIGINAL reschedule URL https://calendo.dev/booking/reschedule.html?token=<TOKEN>. Expect: Calendo recognizes the booking state. (Depending on implementation the same token may now reflect the rescheduled booking, or show an "already rescheduled" notice in #already-state. Record whichever you observe; it must not allow a duplicate/ghost booking.) [L2]

Phase F — L3: confirm the Google Calendar event MOVED + reschedule emails

  1. Action (L3 calendar moved): In Google Calendar (P1), navigate to the ORIGINAL booked date. Expect: There is NO Calendo event at the original time anymore. Then navigate to the NEW date/time from step 19. Expect: The Calendo event now exists at the NEW slot with the same invitee attendee. This proves a MOVE, not a duplicate. Capture screenshots: cu01-11-gcal-old-empty and cu01-12-gcal-moved. [L3]
  1. Action (L3 reschedule emails): In Gmail, search inv-<RUNID> and look for the newest message. Expect: A reschedule-notification email to the invitee referencing the NEW date/time for CU01 Lifecycle <RUNID>, plus a corresponding host-notification email (search CU01 Lifecycle <RUNID>) about the reschedule. Confirm the NEW time is stated. Capture screenshot: cu01-13-email-reschedule. [L3]

Phase G — Cancel via the invitee link

  1. Action: In the invitee context, navigate to the cancel URL: https://calendo.dev/booking/cancel.html?token=<TOKEN>. Expect: The cancel form #cancel-form is visible; the booking details show #event-name = CU01 Lifecycle <RUNID>, #host-name = the host, #date-time = the NEW (rescheduled) slot, #invitee-name = CU01 Invitee <RUNID>. A "Reschedule instead" link (#reschedule-link) pointing to /booking/reschedule.html is also present. [L1]
  1. Action: Fill the reason textarea (#reason) with CU-01 test cancel <RUNID>. Click "Cancel Booking" (#cancel-btn). Expect: The form is replaced by the success state #success-state reading "Your booking has been cancelled successfully." and noting the host has been notified. Capture screenshot: cu01-14-cancel-success. [L1]
  1. Action: Re-open the cancel URL https://calendo.dev/booking/cancel.html?token=<TOKEN> again. Expect: Instead of the cancel form, the #already-state warning appears reading "This booking has already been cancelled." (idempotency — token cannot cancel twice). [L2]
  1. Action: On the P1 dashboard, go to Bookings. Click the "Cancelled" filter (.filter-btn[data-filter="cancelled"]) and wait ~0.5s. Expect: The CU01 Invitee <RUNID> row appears here with a cancelled badge (.badge-cancelled). Then click "Upcoming" (.filter-btn[data-filter="upcoming"]). Expect: The CU01 Invitee <RUNID> row is NOT present in Upcoming. Capture screenshot: cu01-15-dashboard-cancelled. [L2]

Phase H — L3: confirm the Google Calendar event REMOVED + cancellation emails

  1. Action (L3 calendar removed): In Google Calendar (P1), navigate to the NEW (rescheduled) date/time. Expect: The Calendo event is GONE (no event at the rescheduled slot; and still nothing at the original slot). This proves cancellation deletes the real calendar event. Capture screenshot: cu01-16-gcal-removed. [L3]
  1. Action (L3 cancellation emails): In Gmail, search inv-<RUNID> for the newest message. Expect: A cancellation email to the invitee for CU01 Lifecycle <RUNID>, and a corresponding host-notification cancellation email (search CU01 Lifecycle <RUNID>), ideally surfacing the cancellation reason CU-01 test cancel <RUNID>. Capture screenshot: cu01-17-email-cancel. [L3]

L3 reality checks

Cleanup

  1. Booking: Already cancelled in Phase G — no further action needed for the booking itself (it should remain only in the dashboard "Cancelled" filter as a record).
  2. Google Calendar: Verify (per step 29) that no Calendo event remains at the original or rescheduled slots. If any stray test event remains, delete it manually in Google Calendar.
  3. Event type: On the P1 dashboard Overview tab, click "Edit" on the CU01 Lifecycle <RUNID> card (#overviewEventTypes → its Edit button) to open #editEtModal, then click Delete (#editEtDeleteBtn), and confirm in the dialog (#dialogConfirm). Verify #overviewEventTypes no longer contains CU01 Lifecycle <RUNID>.
  4. Emails: Optional — the test emails remain in the P1 Gmail inbox as evidence. Do NOT delete them until the results report has captured the screenshots. If inbox hygiene is desired afterward, they are all findable via search <RUNID>.
  5. Do not touch host availability, settings, or any unrelated event types. Leave the host accounts otherwise unchanged.

Pass/Fail criteria

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

The run FAILS if any L1/L2/L3 check above is missing, if the calendar shows a duplicate instead of a move, if the calendar event persists after cancellation, or if any expected email is absent. If a precondition (Calendo session, Google Calendar connection, Gmail/GCal session, baseline availability) was missing, record as PRECONDITION FAILURE / BLOCKED rather than a product failure.

Evidence to capture

Manual residue / cannot-verify