CU-22: Chrome extension for Gmail (manual-led)

Priority: P3 Accounts/sessions: P1 (ravikantguptaofficial@gmail.com) — logged into Calendo dashboard (https://calendo.dev/dashboard/) AND into the same Google account in Chrome/Gmail (https://mail.google.com). Chrome must allow loading an unpacked Manifest V3 extension. Parallel-safe: Yes — this suite creates no bookings, event types, availability changes, or persistent server state. It only reads the host's existing booking link and writes a value into the extension's local chrome.storage.sync. Exclusive (rewrites global host availability?): No. Estimated time: 20 minutes (≈5 min agent-verifiable backend/link checks + ≈15 min human-led in-Chrome checklist). L3 reality checks: None required by this suite. (No calendar event or email is created. The only external surface is Gmail compose UI, which is part of the manual in-Chrome checklist, not a calendar/email L3 assertion.)

Goal

This suite proves that Calendo's "Insert booking link into Gmail" Chrome extension works end-to-end: a host can save their Calendo booking link in the extension popup, the extension validates it, persists it via chrome.storage.sync (so it follows the user across Chrome profiles/devices), injects a "Calendo" button into every Gmail compose window, and inserts a correctly-formatted, working booking hyperlink at the cursor. This matters because it is the lowest-friction distribution path for the product — a host shares their link straight from their inbox without ever opening the dashboard. Because loading an unpacked MV3 extension and driving the real Gmail DOM is outside what a computer-use browser agent can reliably do, the bulk of this suite is a precise MANUAL checklist for a human; the agent's job is narrowly scoped to confirming that the link the extension produces actually resolves to a live booking page and that the extension's validation contract matches the running site.

Preconditions

Test data

Steps

Part A — Agent-verifiable (backend / link reality)

The computer-use agent runs Steps 1–6. These do NOT require the extension to be installed.

  1. Action: Open the Calendo dashboard overview (https://calendo.dev/dashboard/, sidebar tab data-tab="overview"). Read the host's booking link text from the share bar value element (#bookingLinkValue, inside #bookingLinkBar). Note that #bookingLinkValue may be visually hidden but still contains the URL text — read its text content directly. Expect: A non-empty URL of the form https://calendo.dev/booking/?user=<HOST_SLUG>. Record it as HOST_LINK and extract HOST_SLUG (the value after user=). Capture screenshot: cu22-<RUNID>-dashboard-booking-link. [L1]
  1. Action: In the same dashboard, click the copy-link button (#copyLinkBtn) and observe its feedback. Expect: The button shows a copied/confirmation state (label or toast indicating the link was copied). This confirms the dashboard exposes exactly the link a user would paste into the extension popup's #booking-link field. [L1]
  1. Action: Open HOST_LINK directly in a new browser tab (the full https://calendo.dev/booking/?user=<HOST_SLUG> you recorded). Expect: The public booking page renders (host name/avatar and either a list of event types or a calendar/time-slot UI). It is NOT a 404 or an error page. This proves the link the extension will insert is a real, working destination. Capture screenshot: cu22-<RUNID>-public-booking-page-resolves. [L2]
  1. Action: Also try the pretty form https://calendo.dev/booking/<HOST_SLUG> in a new tab (the Worker rewrites /booking/USERNAME/booking/?user=USERNAME). Expect: Same booking page renders (or cleanly redirects to the ?user= form). Confirms both link shapes a user might save are valid. If the pretty form 404s, note it — the extension popup still accepts /booking/... paths, so record this as a discrepancy rather than a hard fail. [L2]
  1. Action (validation-contract check, read-only): Confirm the extension's link-validation rule matches the live site. The rule in /Users/ravf/projects/calendo/extension/popup.js (isValidCalendoLink) accepts a URL only when hostname === 'calendo.dev' AND pathname starts with /booking/. Verify HOST_LINK satisfies both: hostname is exactly calendo.dev and the path begins with /booking/. Expect: HOST_LINK passes the rule (it is calendo.dev + /booking/...). The negative links https://example.com/booking/notcalendo-<RUNID> (wrong host) and https://calendo.dev/dashboard/<RUNID> (wrong path) would both FAIL the rule. Record this as the expected behavior the manual popup test in Part B must reproduce. [L1]
  1. Action (backend-endpoint note): Confirm there is NO dedicated server-side endpoint for the extension to call. The only extension test in the repo (/Users/ravf/projects/calendo/tests/unit/worker/chrome-extension.test.js) validates static files only (manifest, popup, content script, icons) — it asserts no /api/* route. The extension is fully client-side (MV3 content script + popup using chrome.storage.sync), so the "backend reality" the agent can verify is exactly what Steps 3–4 already covered: the inserted link resolves to a live booking page. Expect: Agent records: "Extension has no backend API endpoint; backend reality = booking link resolves (verified in Steps 3–4)." Capture screenshot: none needed; record as a note. [L2]

Part B — MANUAL in-Chrome checklist (human executes; agent records the human's results)

Steps 7–18 are MANUAL RESIDUE. A computer-use agent cannot load an unpacked extension nor reliably drive Gmail's obfuscated compose DOM. The agent must NOT attempt these; it records the human's pass/fail and screenshots into the report.

  1. Action (manual): In Chrome, open chrome://extensions, toggle "Developer mode" ON (top-right), click "Load unpacked", and select the folder /Users/ravf/projects/calendo/extension/. Expect: A card appears titled "Calendo - Schedule Meetings" (from manifest.json name), version 1.0.0, with the Calendo icon, no load errors. Capture screenshot: cu22-<RUNID>-extension-loaded. [L1, manual]
  1. Action (manual): Click the extension's toolbar icon to open its popup (popup.html). Expect: A 320px-wide white popup with header "Calendo", a labeled text input "Your Calendo booking link" (#booking-link, placeholder https://calendo.dev/booking/username), a blue "Save" button (#save-btn), a grey "Copy Link" button (#copy-btn), an empty status line (#status), and a footer link "Open Calendo Dashboard" → https://calendo.dev/dashboard. Capture screenshot: cu22-<RUNID>-popup-empty. [L1, manual]
  1. Action (manual — negative validation): Type https://example.com/booking/notcalendo-<RUNID> into #booking-link and click "Save" (#save-btn). Expect: The status (#status) shows an error in red: "Please enter a valid Calendo booking URL". Nothing is saved. Capture screenshot: cu22-<RUNID>-popup-reject-wronghost. [L1, manual]
  1. Action (manual — negative validation, wrong path): Clear the field, type https://calendo.dev/dashboard/<RUNID>, click "Save". Expect: Same red error "Please enter a valid Calendo booking URL" (host is right but path is not /booking/). Nothing saved. [L1, manual]
  1. Action (manual — empty validation): Clear the field entirely and click "Save". Expect: Red error "Please enter a booking link". [L1, manual]
  1. Action (manual — happy path save): Paste the exact HOST_LINK recorded in Step 1 (https://calendo.dev/booking/?user=<HOST_SLUG>) into #booking-link and click "Save". Expect: Status (#status) shows green "Link saved!"; the saved-link panel (#current-link) becomes visible showing "Saved link:" followed by the exact HOST_LINK in #saved-url. Capture screenshot: cu22-<RUNID>-popup-saved. [L1, manual]
  1. Action (manual — copy): Click "Copy Link" (#copy-btn), then paste into any plain text field. Expect: Status shows green "Copied to clipboard!" and the pasted text equals HOST_LINK exactly. [L1, manual]
  1. Action (manual — persistence/reload): Close the popup, then reopen it. Expect: #booking-link is pre-filled with HOST_LINK and the #current-link panel still shows it — proving chrome.storage.sync persistence survives popup close/reopen. [L2, manual]
  1. Action (manual — Gmail button injection): Open https://mail.google.com (signed in as P1). Click "Compose". Look at the bottom toolbar of the compose window (the Gmail send-bar, class .btC). Expect: A "Calendo" button (.calendo-insert-btn, blue text, white background) appears at the end of the compose toolbar, next to Gmail's own send-bar icons. Capture screenshot: cu22-<RUNID>-gmail-button-present. [L1, manual]
  1. Action (manual — multiple compose windows): Without closing the first compose window, open a SECOND compose window (Shift+click Compose, or reply to a thread). Expect: Each compose window has its OWN single "Calendo" button — no duplicate buttons in one window, none missing (proves the WeakSet/processedToolbars de-dup and the MutationObserver rescan both work). Capture screenshot: cu22-<RUNID>-gmail-two-composes. [L1, manual]
  1. Action (manual — insert at cursor): In one compose body (.Am.Al.editable), type a sentence, place the cursor at the end, then click the "Calendo" button. Expect: A brief dark tooltip "Link inserted!" appears near the button; the compose body now contains, on its own lines, the text "Book a time with me: " followed by a clickable hyperlink whose visible text AND href both equal HOST_LINK. The link is inserted at/after the cursor, not at a random place. Capture screenshot: cu22-<RUNID>-gmail-link-inserted. [L1, manual]
  1. Action (manual — link correctness + working destination): Hover the inserted hyperlink and confirm its target, then (optionally) send the draft to a plus-alias ravikantguptaofficial+inv-<RUNID>@gmail.com or click the link. Expect: The href is exactly HOST_LINK; clicking it opens the live public booking page (same as Step 3). This closes the loop: the extension inserts a link that actually works. If sent to the plus-alias, confirm the email arrives in the same inbox; deleting that test email is part of Cleanup. [L2, manual]
  1. Action (manual — cross-device sync state, best-effort): If a second Chrome signed into the SAME Google account is available (another machine or profile with the same account + the unpacked extension loaded), open the popup there. If a second device is NOT available, explicitly mark this check as manual residue and skip — do not fabricate a result. Expect (when a second device is available): The popup on the second Chrome auto-fills #booking-link with HOST_LINK without re-typing, proving chrome.storage.sync propagated the value across devices. Capture screenshot: cu22-<RUNID>-sync-second-device. [L2, manual]

L3 reality checks

None — see Pass/Fail. This suite creates no Google/Outlook Calendar events and no transactional Calendo emails. The only "external" surface touched is the Gmail compose DOM (covered in the manual Part B checklist), and the only network reality verified is that the inserted booking link resolves to a live page (Steps 3–4, L2). If the human optionally sends the draft in Step 18, that is a normal Gmail email, not a Calendo-generated L3 email, and requires no calendar/email assertion beyond "it arrived."

Cleanup

  1. Extension storage: Open the extension popup, clear #booking-link, and remove the stored value. The popup has no delete button, so clear via DevTools: on the popup, right-click → Inspect → Console, run chrome.storage.sync.remove('calendoBookingLink'). Reopen the popup and confirm #booking-link is empty and the #current-link panel is hidden.
  2. Unpacked extension: In chrome://extensions, click "Remove" on the "Calendo - Schedule Meetings" card so no test extension lingers (especially on shared machines). Optionally toggle "Developer mode" back OFF if it was off before.
  3. Gmail drafts/emails: Discard any compose drafts created in Steps 15–18. If a test email was sent to ravikantguptaofficial+inv-<RUNID>@gmail.com in Step 18, search Gmail for inv-<RUNID>, delete the message (Trash → empty), so the host inbox stays clean.
  4. No Calendo server objects were created — nothing to delete on the dashboard. Confirm: no new bookings, event types, or routing forms reference <RUNID>.

Pass/Fail criteria

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

The run FAILS if any agent-verifiable step (A1–A3) fails, OR if a precondition is missing and was not present (flag, not pass). Steps B1–B6 failing constitute a manual fail; if the human could not run Part B at all (no dev-mode Chrome / no Gmail session), the suite result is "Partial — agent steps passed, manual portion not run" and B-items move to manual residue.

Evidence to capture

Manual residue / cannot-verify