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
- Logged-in session — Calendo dashboard: P1 is signed into
https://calendo.dev/dashboard/(already-active persisted session). The agent needs this only to read the host's real booking link from#bookingLinkValue. If the dashboard loads to the login/marketing page instead of the app shell (#appLayout/#welcomeHeadernot visible), this is a precondition failure — FLAG it per00-setup-preconditions.mdand do NOT attempt a cold login. - Logged-in session — Gmail (manual portion only): The human running the in-Chrome checklist must be signed into
https://mail.google.comasravikantguptaofficial@gmail.com. If not signed in, FLAG and stop the manual portion; do not enter passwords on behalf of the account. - Chrome with developer mode (manual portion only): A desktop Chrome where the human can open
chrome://extensions, toggle "Developer mode" on, and "Load unpacked". If the machine/policy forbids unpacked extensions (managed/enterprise Chrome), this entire in-Chrome portion is unverifiable — record as manual residue and stop. - Extension source present on disk (manual portion only): The unpacked extension at
/Users/ravf/projects/calendo/extension/(files:manifest.json,popup.html,popup.js,content.js,styles.css,icons/icon-16.png,icons/icon-48.png,icons/icon-128.png). If any file is missing, FLAG — do not improvise a substitute. - Baseline host data: P1's Calendo account already has a public booking page slug surfaced at
#bookingLinkValue. No event type creation is required; the extension only needs the base page link. If#bookingLinkValueis empty, FLAG (the host has no published booking page) and skip to Manual residue.
Test data
- RUNID: Pick one fresh UTC token at execution time, e.g.
20260601-1530. Use it verbatim in every note/screenshot name so reruns never collide. - Host booking link (read, not created): Whatever
#bookingLinkValueshows on the dashboard, expected to look likehttps://calendo.dev/booking/?user=<HOST_SLUG>(the site may also accept the pretty formhttps://calendo.dev/booking/<HOST_SLUG>). Record the exact string asHOST_LINKand the slug portion (afteruser=) asHOST_SLUG. - Negative-test link (for popup validation):
https://example.com/booking/notcalendo-<RUNID>— must be REJECTED by the popup. Also testhttps://calendo.dev/dashboard/<RUNID>— must be REJECTED (right host, wrong path). - No persistent server objects are created by this suite. The only stored state is the extension's
chrome.storage.sync.calendoBookingLinkvalue, cleared during Cleanup.
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.
- Action: Open the Calendo dashboard overview (
https://calendo.dev/dashboard/, sidebar tabdata-tab="overview"). Read the host's booking link text from the share bar value element (#bookingLinkValue, inside#bookingLinkBar). Note that#bookingLinkValuemay be visually hidden but still contains the URL text — read its text content directly. Expect: A non-empty URL of the formhttps://calendo.dev/booking/?user=<HOST_SLUG>. Record it asHOST_LINKand extractHOST_SLUG(the value afteruser=). Capture screenshot:cu22-<RUNID>-dashboard-booking-link. [L1]
- 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-linkfield. [L1]
- Action: Open
HOST_LINKdirectly in a new browser tab (the fullhttps://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]
- 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]
- 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 whenhostname === 'calendo.dev'ANDpathnamestarts with/booking/. VerifyHOST_LINKsatisfies both: hostname is exactlycalendo.devand the path begins with/booking/. Expect:HOST_LINKpasses the rule (it iscalendo.dev+/booking/...). The negative linkshttps://example.com/booking/notcalendo-<RUNID>(wrong host) andhttps://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]
- 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 usingchrome.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.
- 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" (frommanifest.jsonname), version1.0.0, with the Calendo icon, no load errors. Capture screenshot:cu22-<RUNID>-extension-loaded. [L1, manual]
- 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, placeholderhttps://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]
- Action (manual — negative validation): Type
https://example.com/booking/notcalendo-<RUNID>into#booking-linkand 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]
- 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]
- Action (manual — empty validation): Clear the field entirely and click "Save". Expect: Red error "Please enter a booking link". [L1, manual]
- Action (manual — happy path save): Paste the exact
HOST_LINKrecorded in Step 1 (https://calendo.dev/booking/?user=<HOST_SLUG>) into#booking-linkand click "Save". Expect: Status (#status) shows green "Link saved!"; the saved-link panel (#current-link) becomes visible showing "Saved link:" followed by the exactHOST_LINKin#saved-url. Capture screenshot:cu22-<RUNID>-popup-saved. [L1, manual]
- 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 equalsHOST_LINKexactly. [L1, manual]
- Action (manual — persistence/reload): Close the popup, then reopen it. Expect:
#booking-linkis pre-filled withHOST_LINKand the#current-linkpanel still shows it — provingchrome.storage.syncpersistence survives popup close/reopen. [L2, manual]
- 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]
- 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/processedToolbarsde-dup and theMutationObserverrescan both work). Capture screenshot:cu22-<RUNID>-gmail-two-composes. [L1, manual]
- 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 ANDhrefboth equalHOST_LINK. The link is inserted at/after the cursor, not at a random place. Capture screenshot:cu22-<RUNID>-gmail-link-inserted. [L1, manual]
- 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.comor click the link. Expect: The href is exactlyHOST_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]
- 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-linkwithHOST_LINKwithout re-typing, provingchrome.storage.syncpropagated 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
- 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, runchrome.storage.sync.remove('calendoBookingLink'). Reopen the popup and confirm#booking-linkis empty and the#current-linkpanel is hidden. - 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. - Gmail drafts/emails: Discard any compose drafts created in Steps 15–18. If a test email was sent to
ravikantguptaofficial+inv-<RUNID>@gmail.comin Step 18, search Gmail forinv-<RUNID>, delete the message (Trash → empty), so the host inbox stays clean. - 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:
- (Agent, A1)
#bookingLinkValuereturns a non-emptyhttps://calendo.dev/booking/?user=<HOST_SLUG>link, and#copyLinkBtncopies it. - (Agent, A2) Opening
HOST_LINK(and the pretty/booking/<HOST_SLUG>form) renders a live public booking page, not a 404/error. - (Agent, A3)
HOST_LINKsatisfies the extension's validation contract (hostname === 'calendo.dev'and path starts with/booking/); the two negative links would be rejected by that rule. - (Manual, B1) The unpacked extension loads with name "Calendo - Schedule Meetings", v1.0.0, no errors.
- (Manual, B2) The popup REJECTS the wrong-host link, the wrong-path link, and an empty field with the correct red error messages; it ACCEPTS
HOST_LINKwith green "Link saved!" and shows it in#saved-url. - (Manual, B3) Saved link persists across popup close/reopen (
chrome.storage.sync). - (Manual, B4) A single "Calendo" button (
.calendo-insert-btn) is injected into each Gmail compose toolbar, including a second simultaneous compose window (no duplicates, none missing). - (Manual, B5) Clicking the button inserts "Book a time with me: " plus a hyperlink whose visible text and
hrefboth equalHOST_LINK, at the cursor, with a "Link inserted!" tooltip. - (Manual, B6) The inserted link opens the live booking page when clicked.
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
cu22-<RUNID>-dashboard-booking-link— dashboard#bookingLinkValueshowingHOST_LINK.cu22-<RUNID>-public-booking-page-resolves—HOST_LINKrendering a live booking page.- Note: recorded
HOST_LINKandHOST_SLUGstrings (text). - Note: "Extension has no backend API endpoint; backend reality = link resolves (Steps 3–4)."
cu22-<RUNID>-extension-loaded—chrome://extensionscard (manual).cu22-<RUNID>-popup-emptyandcu22-<RUNID>-popup-saved— popup before/after save (manual).cu22-<RUNID>-popup-reject-wronghost— validation rejection (manual).cu22-<RUNID>-gmail-button-presentandcu22-<RUNID>-gmail-two-composes— injected button(s) (manual).cu22-<RUNID>-gmail-link-inserted— inserted snippet with hyperlink (manual).cu22-<RUNID>-sync-second-device— cross-device popup auto-fill, only if a second device was available (manual).- A short written verdict line per Pass/Fail item (A1–A3, B1–B6) with pass/fail/not-run.
Manual residue / cannot-verify
- Loading the unpacked MV3 extension (
chrome://extensions→ Developer mode → Load unpacked from/Users/ravf/projects/calendo/extension/) — a computer-use agent cannot do this; human only. - Driving the Gmail compose DOM — Gmail's obfuscated, frequently-changing class names (
.btC,.Am.Al.editable,.M9) and dynamic compose windows make reliable agent automation impractical; human verifies button injection, the "Link inserted!" tooltip, cursor-position insertion, and the multi-compose de-dup behavior. - Cross-device
chrome.storage.syncpropagation (Step 19) — requires two Chrome instances signed into the same Google account; cannot be verified from a single agent session. If only one device exists, this is unverified residue, not a fail. chrome.storage.syncinternals / quota / actual Google sync timing — the test only proves persistence within a profile; true cloud-sync latency and quota limits are not in scope (TBD).- Chrome Web Store listing / packed-extension distribution — out of scope; this suite tests the unpacked dev build only.