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
- P1 Calendo session active. Browser must already be logged into Calendo as P1 at https://calendo.dev/dashboard/. Confirm by loading the dashboard and seeing the sidebar +
#welcomeHeader. If you are bounced to/auth/login.html, STOP and FLAG a precondition failure (see00-setup-preconditions.md). Do NOT attempt a cold email/password or Google password login. - P1 Google Calendar connected to Calendo. Required for L3 calendar checks. Verify on the Overview tab: the calendar-connect chip area (
#calendarConnectBtns) should NOT be offering "+ Google" as the only state — a connected Google calendar should be shown. If it still shows "Connect Google Calendar" / "+ Google", FLAG it: the L3 calendar checks cannot pass, note this in the report, and do not improvise an OAuth connect. - P1 Gmail session active in a separate tab at https://mail.google.com (this is the host inbox AND, via plus-aliasing, the invitee inbox). If not logged in, FLAG; do not log in with a password.
- P1 Google Calendar session active at https://calendar.google.com for the L3 calendar checks. If not logged in, FLAG.
- Baseline host availability exists. P1 must have Mon–Fri working hours so the public calendar shows available days. This suite does NOT create or modify availability. If the public booking calendar shows zero available days across 6 months of forward navigation, FLAG a precondition failure (availability missing) rather than treating it as a product bug here.
- A bookable event type exists or will be created. This suite creates its own dedicated event type (so it is self-contained and parallel-safe). It does NOT depend on the default "30 Minute Meeting", though that default may also exist.
- If ANY precondition is missing: record it as a precondition failure in the results report and skip the dependent steps. Do not improvise logins, do not connect calendars, do not change availability.
Test data
- RUNID: pick a fresh UTC timestamp token at execution time, e.g.
20260601-1530. Use the SAME RUNID for every artifact in this run. - Event type name:
CU01 Lifecycle <RUNID>(e.g.CU01 Lifecycle 20260601-1530) - Event type slug (auto-derived by Calendo's slugify):
cu01-lifecycle-<RUNID>(lowercase, spaces → hyphens). You will read the real slug back; do not hardcode if it differs. - Event duration: 30 minutes
- Event description:
CU-01 automated lifecycle test <RUNID> - Invitee name:
CU01 Invitee <RUNID> - Invitee email (plus-alias of host inbox):
ravikantguptaofficial+inv-<RUNID>@gmail.com - Cancellation reason (used at cancel step):
CU-01 test cancel <RUNID> - Host slug: read live from the dashboard booking link (
#bookingLinkValue, the part afteruser=). Do not assume it.
Steps
Phase A — Read host slug and create the dedicated event type (as P1)
- 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-overviewhas classactive). Expect: Dashboard chrome loads;#welcomeHeaderis visible;#overviewEventTypeslists existing event types (e.g. contains "Minute"). [L1]
- 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 afteruser=. Expect: A non-empty URL containing/booking/?user=. Capture screenshot:cu01-01-dashboard-overview. [L2]
- Action: Click the "+ New" event-type button (
#overviewCreateEtBtn). Expect: The event edit modal#editEtModalbecomes visible (in create mode; its save button reads "Create"). [L1]
- Action: In the modal, fill Name (
#editEtName) withCU01 Lifecycle <RUNID>; set Duration (#editEtDuration) to30; fill Description (#editEtDesc) withCU-01 automated lifecycle test <RUNID>. Expect: Fields show the typed values. [L1]
- Action: Click Save/Create (
#editEtSaveBtn). Wait ~2s for the overview to re-render. Expect: The modal closes and#overviewEventTypesnow containsCU01 Lifecycle <RUNID>. Capture screenshot:cu01-02-event-created. [L2]
Phase B — Book the slot as an anonymous invitee
- 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-viewis 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-indicatorshows "Select Time". [L1]
- Action: In the calendar (
#calendar), find the first available day. Available days have classcalendar-day available(clickable, dark text); unavailable days havecalendar-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-panelbecomes visible and shows one or more.time-slotbuttons. [L1]
- 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-viewbecomes visible;#confirm-datetime-textshows the selected date/time; the step indicator advances to "Your Details". Capture screenshot:cu01-03-slot-selected. [L1]
- 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]
- Action: Click "Confirm Booking" (
#btn-confirm). Wait for the network call to complete. Expect: The confirmation view#confirmation-success-viewappears;.confirmation-titlereads exactly "Booking Confirmed!";#confirmation-detailscontains the event nameCU01 Lifecycle <RUNID>, "Duration 30 min", the booked Date, the booked Time range, the Timezone, and the Host name. [L1]
- Action: On the confirmation page, locate the manage links block
#confirmation-manage. Expect: It contains a "Reschedule" link whosehrefmatches/booking/reschedule.html?token=...and a "Cancel booking" link whosehrefmatches/booking/cancel.html?token=.... Both share the SAME token. RECORD this token string (thetoken=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
- 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-bookingsis active; the "Upcoming" filter button (.filter-btn[data-filter="upcoming"]) is active by default. [L1]
- Action: In the bookings table (
#bookingsTableContainer), find the row for this booking. Expect: A row shows invitee nameCU01 Invitee <RUNID>, event nameCU01 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]
- 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
- 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 inviteeravikantguptaofficial+inv-<RUNID>@gmail.comlisted as a guest/attendee. Capture screenshot:cu01-06-gcal-created. [L3]
- 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 toravikantguptaofficial+inv-<RUNID>@gmail.comis present, with a subject indicating a confirmed booking forCU01 Lifecycle <RUNID>, the correct date/time in the body, and working Reschedule and Cancel links. Capture screenshot:cu01-07-email-invitee-confirm. [L3]
- Action (L3 host email): In Gmail, search exactly:
CU01 Lifecycle <RUNID>(and/orsubject:(new booking)). Expect: A host-notification email in the P1 inbox announcing the new booking fromCU01 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
- 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-uiis visible;#current-booking-infoshows the CURRENT booking —#current-eventcontainsCU01 Lifecycle <RUNID>and#current-datetimeshows the originally-booked slot; a calendar#calendaris shown for picking a new time. [L1]
- 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-btnif needed). Click an available day, then click a.time-slotthat is NOT the original time. Record this as the NEW SLOT (date + time). Expect: The confirm view#confirm-viewappears;#confirm-datetimeshows the new time range and date;#confirm-eventshows the event name. Capture screenshot:cu01-09-reschedule-newtime-selected. [L1]
- Action: Click "Confirm Reschedule" (
#btn-reschedule). Wait for the request to finish. Expect: The success state#success-stateappears;.confirmation-titlereads "Booking Rescheduled";#confirmation-detailsshows the NEW date/time, the invitee name, and the invitee email. Capture screenshot:cu01-10-reschedule-success. [L1]
- 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]
- 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
- 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-emptyandcu01-12-gcal-moved. [L3]
- 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 forCU01 Lifecycle <RUNID>, plus a corresponding host-notification email (searchCU01 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
- Action: In the invitee context, navigate to the cancel URL:
https://calendo.dev/booking/cancel.html?token=<TOKEN>. Expect: The cancel form#cancel-formis 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.htmlis also present. [L1]
- Action: Fill the reason textarea (
#reason) withCU-01 test cancel <RUNID>. Click "Cancel Booking" (#cancel-btn). Expect: The form is replaced by the success state#success-statereading "Your booking has been cancelled successfully." and noting the host has been notified. Capture screenshot:cu01-14-cancel-success. [L1]
- Action: Re-open the cancel URL
https://calendo.dev/booking/cancel.html?token=<TOKEN>again. Expect: Instead of the cancel form, the#already-statewarning appears reading "This booking has already been cancelled." (idempotency — token cannot cancel twice). [L2]
- Action: On the P1 dashboard, go to Bookings. Click the "Cancelled" filter (
.filter-btn[data-filter="cancelled"]) and wait ~0.5s. Expect: TheCU01 Invitee <RUNID>row appears here with a cancelled badge (.badge-cancelled). Then click "Upcoming" (.filter-btn[data-filter="upcoming"]). Expect: TheCU01 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
- 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]
- Action (L3 cancellation emails): In Gmail, search
inv-<RUNID>for the newest message. Expect: A cancellation email to the invitee forCU01 Lifecycle <RUNID>, and a corresponding host-notification cancellation email (searchCU01 Lifecycle <RUNID>), ideally surfacing the cancellation reasonCU-01 test cancel <RUNID>. Capture screenshot:cu01-17-email-cancel. [L3]
L3 reality checks
- Google Calendar (https://calendar.google.com, logged in as P1):
- After booking (step 15): on the BOOKED date at the booked time, a 30-min event exists titled for
CU01 Lifecycle <RUNID>with inviteeravikantguptaofficial+inv-<RUNID>@gmail.comas a guest. - After reschedule (step 23): original slot is EMPTY; NEW slot has the event with the same invitee → it MOVED.
- After cancel (step 29): NEW slot is EMPTY (and original still empty) → it was DELETED.
- After booking (step 15): on the BOOKED date at the booked time, a 30-min event exists titled for
- Gmail (https://mail.google.com, P1 inbox = both host and invitee via plus-alias):
- Confirmation (steps 16–17): search
inv-<RUNID>→ invitee confirmation email; searchCU01 Lifecycle <RUNID>→ host new-booking email. Confirm date/time and working Reschedule/Cancel links. - Reschedule (step 24): newest
inv-<RUNID>message references the NEW time; matching host reschedule notification exists. - Cancellation (step 30): newest
inv-<RUNID>message is a cancellation; matching host cancellation notification exists; reasonCU-01 test cancel <RUNID>ideally present.
- Confirmation (steps 16–17): search
- If the calendar was flagged as NOT connected in Preconditions, mark all calendar L3 checks as BLOCKED (not failed) and proceed with email + dashboard checks only.
Cleanup
- 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).
- 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.
- 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#overviewEventTypesno longer containsCU01 Lifecycle <RUNID>. - 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>. - 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:
- [L1] Booking confirmation page showed "Booking Confirmed!" with the correct event, 30-min duration, booked date/time, timezone, and host, plus working Reschedule and Cancel links sharing one token.
- [L2] The booking appeared in the dashboard Bookings → Upcoming with the correct invitee name, event name, booked time, and a confirmed badge, AND survived a full page reload.
- [L3] A real Google Calendar event was created at the booked slot with the invitee as attendee.
- [L3] Both the invitee confirmation email (search
inv-<RUNID>) and the host new-booking email (searchCU01 Lifecycle <RUNID>) arrived. - [L1] Reschedule via the invitee link succeeded ("Booking Rescheduled" with the new time).
- [L2] Dashboard Upcoming reflected the NEW time for the invitee; old time gone.
- [L3] The Google Calendar event MOVED to the new slot (original empty, new slot has it, no duplicate).
- [L3] Reschedule emails (invitee + host) arrived referencing the new time.
- [L1] Cancel via the invitee link succeeded ("cancelled successfully"); re-opening the cancel link showed an "already cancelled" state.
- [L2] Dashboard showed the booking under Cancelled (with cancelled badge) and NOT under Upcoming.
- [L3] The Google Calendar event was REMOVED after cancellation.
- [L3] Cancellation emails (invitee + host) arrived.
- Cleanup completed: the
CU01 Lifecycle <RUNID>event type was deleted and no stray calendar events remain.
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
cu01-01-dashboard-overview— Overview with host booking link visible.cu01-02-event-created— event typeCU01 Lifecycle <RUNID>in the overview list.cu01-03-slot-selected— confirm form with selected date/time.cu01-04-booking-confirmed— confirmation page with details + manage links.cu01-05-dashboard-booking-row— Upcoming booking row with confirmed badge.cu01-06-gcal-created— Google Calendar event at booked slot showing invitee attendee.cu01-07-email-invitee-confirmandcu01-08-email-host-confirm— confirmation emails.cu01-09-reschedule-newtime-selectedandcu01-10-reschedule-success— reschedule flow.cu01-11-gcal-old-emptyandcu01-12-gcal-moved— calendar move evidence.cu01-13-email-reschedule— reschedule emails.cu01-14-cancel-success— cancel success state.cu01-15-dashboard-cancelled— cancelled filter row + absence from upcoming.cu01-16-gcal-removed— calendar event gone after cancel.cu01-17-email-cancel— cancellation emails.- Notes to record: the RUNID used, the resolved HOST_SLUG, the resolved event slug, the booked slot (date+time), the new slot after reschedule, and the manage token.
Manual residue / cannot-verify
- Email deliverability/spam placement & exact rendering across clients: the agent only confirms the message exists in Gmail's web UI; whether emails would land in spam for an arbitrary recipient, or render correctly in non-Gmail clients, is not verifiable here.
- ICS file contents: the "Download .ics" button downloads a file; the agent cannot reliably open and parse the downloaded ICS to confirm DTSTART/DTEND correctness in-browser. Hand off to a human if ICS correctness must be verified.
- Calendar invite RSVP semantics: whether the invitee (a plus-alias of the host's own Gmail) receives a true Google Calendar invitation vs. only an event on the host's calendar is ambiguous because invitee and host share the underlying mailbox. A truly separate invitee mailbox (e.g. P3
everythingaichannelemail@gmail.com) would be needed to fully verify attendee-side invite delivery — out of scope here, noted for a dedicated suite. - Reminder emails (24h/1h before): not exercised by this suite because the booked slot is in the near future but reminders fire on a schedule; verifying reminders requires time-based observation — out of scope (TBD, separate suite).
- Timezone correctness across non-local zones: this suite uses the invitee browser's default timezone; cross-timezone slot math is out of scope here.