Conversation
Adds the Calendly booking widget to the registry as `useScriptCalendly`, covering inline, popup, and badge embeds with first-party proxy support for `assets.calendly.com` and Partytown forwards for the widget initialisers.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
📝 WalkthroughWalkthroughThis pull request introduces Calendly scheduling widget integration to Nuxt Scripts. The implementation provides a useScriptCalendly() composable that handles loading the Calendly external widget script with optional bundling and proxying, injects the widget stylesheet, and exposes a typed proxy wrapping window.Calendly methods. The integration includes widget variants for inline, popup, and badge embeds with support for prefill and UTM tracking options. A stub queue mechanism queues method calls until the SDK loads. The PR adds user documentation, playground demonstrations, comprehensive E2E test coverage for both bundled and CDN delivery modes, unit tests validating the stub queue and proxy configuration, and privacy tier declarations marking Calendly as PRIVACY_IP_ONLY with domain-level IP anonymization. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
FIRST_PARTY.md (1)
116-148:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winClarify Calendly’s iframe exception in the table.
Current wording can imply all Calendly traffic is first-party proxied, but the booking iframe remains direct to
calendly.com. Add a brief note to avoid privacy-scope confusion.✏️ Suggested doc clarification
-| `calendly` | calendly | `PRIVACY_IP_ONLY` | Path A | +| `calendly` | calendly | `PRIVACY_IP_ONLY` | Path A (script asset proxying; booking iframe remains direct to calendly.com) |🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@FIRST_PARTY.md` around lines 116 - 148, The table entry for the `calendly` config key (row showing `calendly` | `PRIVACY_IP_ONLY` | Path A) is ambiguous about Calendly’s iframe being proxied; update that row to add a concise clarifying note that while general Calendly integrations are treated as PRIVACY_IP_ONLY via Path A, the booking iframe loads directly from calendly.com and is not proxied (e.g., append “— booking iframe loads directly from calendly.com, not proxied” or similar) so readers don’t assume full first‑party proxy coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/script/src/runtime/registry/calendly.ts`:
- Around line 34-40: The CalendlyInlineWidgetOptions interface currently types
parentElement as HTMLElement | string which allows invalid selector strings;
change parentElement to only HTMLElement in the CalendlyInlineWidgetOptions
declaration so callers must pass a DOM element, and update any call sites or
helper functions that constructed or accepted selector strings to instead
resolve the element (e.g., querySelector) before passing it to the
CalendlyInlineWidgetOptions constructor or to the functions that consume
parentElement; ensure references to parentElement in functions like the inline
widget renderer treat it as an HTMLElement (no string handling) and add runtime
checks where appropriate to throw a clear error if a non-HTMLElement is
supplied.
---
Outside diff comments:
In `@FIRST_PARTY.md`:
- Around line 116-148: The table entry for the `calendly` config key (row
showing `calendly` | `PRIVACY_IP_ONLY` | Path A) is ambiguous about Calendly’s
iframe being proxied; update that row to add a concise clarifying note that
while general Calendly integrations are treated as PRIVACY_IP_ONLY via Path A,
the booking iframe loads directly from calendly.com and is not proxied (e.g.,
append “— booking iframe loads directly from calendly.com, not proxied” or
similar) so readers don’t assume full first‑party proxy coverage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3e0facc9-42af-4d83-bbe3-70f9832d2578
📒 Files selected for processing (25)
FIRST_PARTY.mddocs/content/docs/1.guides/2.first-party.mddocs/content/scripts/calendly.mdpackages/script/src/registry-logos.tspackages/script/src/registry-types.jsonpackages/script/src/registry.tspackages/script/src/runtime/registry/calendly.tspackages/script/src/runtime/registry/schemas.tspackages/script/src/runtime/types.tspackages/script/src/script-meta.tsplayground/pages/index.vueplayground/pages/third-parties/calendly/default.vueplayground/pages/third-parties/calendly/nuxt-scripts.vuetest/e2e/_calendly-suite.tstest/e2e/calendly-cdn.test.tstest/e2e/calendly.test.tstest/fixtures/calendly-cdn/nuxt.config.tstest/fixtures/calendly-cdn/tsconfig.jsontest/fixtures/calendly/app.vuetest/fixtures/calendly/nuxt.config.tstest/fixtures/calendly/package.jsontest/fixtures/calendly/pages/index.vuetest/fixtures/calendly/tsconfig.jsontest/types/types.test-d.tstest/unit/proxy-configs.test.ts
The calendly e2e tests failed in CI because: - The fixtures lacked a `prepare:fixtures` entry, so `.nuxt/tsconfig.json` was missing when Vite parsed the page that imports `useScriptCalendly`. - The cdn fixture had no per-call `bundle: false`, so the script was always served from /_scripts/assets/ (proxy) instead of the CDN. - The stub queue e2e was racing the real script load (`onNuxtReady`) and is now a deterministic unit test. Restructure pages to mirror the linkedin-insight fixtures: empty index, composable usage on `/calendly`. cdn fixture now overrides the page + registry config to disable bundling. Add unit test guarding the multi-arg push regression (#741) on the stub queue.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
test/unit/calendly-stub-queue.test.ts (1)
53-62: ⚡ Quick winMissing queue-length guard in the multi-arg test
The second test asserts the shape of
stub.q[0]but never verifies that exactly one entry was enqueued. If a future change to the stub accidentally pushes the entry multiple times (e.g., once per arg), the assertion onq[0]alone would still pass.🛡️ Proposed fix
it('preserves multiple positional args (showPopupWidget(url, ...))', () => { const stub = createStub() stub.showPopupWidget('https://calendly.com/example/30min', { foo: 'bar' }, 42) + expect(stub.q).toHaveLength(1) expect(stub.q[0]).toEqual([ 'showPopupWidget', 'https://calendly.com/example/30min', { foo: 'bar' }, 42, ]) })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/unit/calendly-stub-queue.test.ts` around lines 53 - 62, The test for createStub's showPopupWidget should assert the queue length before inspecting stub.q[0]; add an explicit expectation that stub.q.length === 1 (or toEqual(1)) immediately after calling stub.showPopupWidget to ensure exactly one entry was enqueued, then keep the existing equality check on stub.q[0]; reference createStub, stub.showPopupWidget and stub.q when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/fixtures/calendly/pages/calendly.vue`:
- Around line 11-14: The call to proxy.Calendly.initInlineWidget uses a CSS
selector string for parentElement but Calendly expects an actual DOM node;
change the invocation so parentElement is a resolved HTMLElement (e.g., obtain
the element via document.getElementById('calendly-host') or
document.querySelector('#calendly-host') and pass that variable into
proxy.Calendly.initInlineWidget) to ensure the widget mounts inside the intended
container.
---
Nitpick comments:
In `@test/unit/calendly-stub-queue.test.ts`:
- Around line 53-62: The test for createStub's showPopupWidget should assert the
queue length before inspecting stub.q[0]; add an explicit expectation that
stub.q.length === 1 (or toEqual(1)) immediately after calling
stub.showPopupWidget to ensure exactly one entry was enqueued, then keep the
existing equality check on stub.q[0]; reference createStub, stub.showPopupWidget
and stub.q when making this change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 47210afd-2374-4e5b-b53e-cc841be0a607
📒 Files selected for processing (8)
package.jsontest/e2e/_calendly-suite.tstest/fixtures/calendly-cdn/nuxt.config.tstest/fixtures/calendly-cdn/pages/calendly.vuetest/fixtures/calendly/nuxt.config.tstest/fixtures/calendly/pages/calendly.vuetest/fixtures/calendly/pages/index.vuetest/unit/calendly-stub-queue.test.ts
✅ Files skipped from review due to trivial changes (3)
- package.json
- test/fixtures/calendly/nuxt.config.ts
- test/fixtures/calendly-cdn/pages/calendly.vue
🚧 Files skipped from review as they are similar to previous changes (3)
- test/fixtures/calendly/pages/index.vue
- test/fixtures/calendly-cdn/nuxt.config.ts
- test/e2e/_calendly-suite.ts
Calendly's `initInlineWidget` API requires a DOM element reference; CSS selector strings are not supported. Tighten the type and update fixtures to resolve the element via `querySelector` first. Per CodeRabbit review on #750.
The composable injected `<link rel=stylesheet href=https://assets.calendly.com/assets/external/widget.css>` which leaked the visitor IP to the vendor on every page render — and the `url(/assets/external/close-icon.svg)` reference inside that stylesheet leaked again on every popup-close. This bypassed the bundle/proxy posture the registry advertises (`proxy.privacy: PRIVACY_IP_ONLY`). Inlines the 2.4 KB stylesheet via `useHead({ style })`, with the close-icon SVG embedded as a data URI. No more requests to `assets.calendly.com` at any point in the widget lifecycle. Adds `<ScriptCalendlyInlineWidget>` for the inline embed shape (popup and badge stay composable-only, since they have no host element). Mirrors `<ScriptYouTubePlayer>` — visibility trigger by default, `above-the-fold` preconnect, slots for loading/awaiting/error. Hardens the e2e suite to match the Ahrefs bar: asserts no `assets.calendly.com` stylesheet link is present, that the inline `<style>` carries the data-URI close icon, and that `initInlineWidget` mounts a real iframe in the requested parentElement. Adds a Node-fetch contract test that asserts the bundled artefact still exports the widget API and contains no `assets.calendly.com` references. Docs aligned with the post-#751 layout (frontmatter component link, `script-types` at end, code-group for proxy/onLoaded, dedicated `<ScriptCalendlyInlineWidget>` section).
CodeQL flagged the substring `.includes('assets.calendly.com')` as
js/incomplete-url-substring-sanitization (false positive — this is a
test-side leak detector, not a server-side allowlist), but the lint
gates the PR. Parse with `new URL(l.href).hostname` to satisfy it.
# Conflicts: # FIRST_PARTY.md # docs/content/docs/1.guides/2.first-party.md # package.json # packages/script/src/registry-types.json # packages/script/src/runtime/registry/schemas.ts # packages/script/src/runtime/types.ts # playground/pages/index.vue # test/unit/proxy-configs.test.ts
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/script/src/runtime/components/ScriptCalendlyInlineWidget.vue (1)
10-31: ⚡ Quick win
CalendlyPrefill,CalendlyUtm, andCalendlyPageSettingsare redeclared locally instead of imported.These three interfaces are also defined in
calendly.ts(non-exported), forcing duplication. More importantly, the localCalendlyPageSettingshere addshideGdprBanner(line 28) which is absent from the version incalendly.ts, creating a silent type divergence: users of the rawuseScriptCalendlycomposable can't type-safely passhideGdprBannertoinitInlineWidgetoptions, even though Calendly's SDK accepts it.Exporting the canonical types from
calendly.ts(after addinghideGdprBannerthere) and importing them here removes the duplication and the inconsistency in one step.♻️ Proposed changes
In
calendly.ts, export the shared interfaces and add the missing field:-interface CalendlyPrefill { +export interface CalendlyPrefill { name?: string email?: string firstName?: string lastName?: string customAnswers?: Record<string, string> } -interface CalendlyUtm { +export interface CalendlyUtm { utmCampaign?: string utmSource?: string utmMedium?: string utmContent?: string utmTerm?: string } -interface CalendlyPageSettings { +export interface CalendlyPageSettings { backgroundColor?: string hideEventTypeDetails?: boolean hideLandingPageDetails?: boolean + hideGdprBanner?: boolean primaryColor?: string textColor?: string }In
ScriptCalendlyInlineWidget.vue, replace the local re-declarations with imports:-interface CalendlyPrefill { - name?: string - email?: string - firstName?: string - lastName?: string - customAnswers?: Record<string, string> -} -interface CalendlyUtm { - utmCampaign?: string - utmSource?: string - utmMedium?: string - utmContent?: string - utmTerm?: string -} -interface CalendlyPageSettings { - backgroundColor?: string - hideEventTypeDetails?: boolean - hideLandingPageDetails?: boolean - hideGdprBanner?: boolean - primaryColor?: string - textColor?: string -} +import type { CalendlyPrefill, CalendlyUtm, CalendlyPageSettings } from '../registry/calendly'🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/script/src/runtime/components/ScriptCalendlyInlineWidget.vue` around lines 10 - 31, The three local interfaces (CalendlyPrefill, CalendlyUtm, CalendlyPageSettings) in ScriptCalendlyInlineWidget.vue duplicate types defined in calendly.ts and introduce a missing field (hideGdprBanner) divergence; fix by exporting the canonical interfaces from calendly.ts (add hideGdprBanner to CalendlyPageSettings there), then remove the local redeclarations and import CalendlyPrefill, CalendlyUtm, and CalendlyPageSettings into ScriptCalendlyInlineWidget.vue so useScriptCalendly/initInlineWidget consumers have a single, type-safe source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/script/src/runtime/components/ScriptCalendlyInlineWidget.vue`:
- Around line 92-95: The watch on status (watch(status, (s) => { if (s ===
'error') emit('error') })) can miss a pre-existing error because it registers
with the default immediate: false; update the component so that the current
status is checked at registration by either adding the watch option { immediate:
true } or explicitly checking if status === 'error' and calling emit('error')
during setup/mount; reference the existing watch(status, ...) and the
emit('error') call when making the change.
In `@packages/script/src/runtime/registry/calendly.ts`:
- Around line 26-32: The CalendlyPageSettings interface is missing the
hideGdprBanner property which causes a type mismatch with
ScriptCalendlyInlineWidget.vue and prevents callers of useScriptCalendly (and
types CalendlyInlineWidgetOptions / CalendlyPopupWidgetOptions that compose
CalendlyPageSettings) from passing this flag type-safely; update the
CalendlyPageSettings interface to include hideGdprBanner?: boolean so the
exported types align with the local copy in ScriptCalendlyInlineWidget.vue and
downstream option types accept the field.
- Around line 89-103: Remove the module-level boolean cssInjected and its
short-circuit in ensureCalendlyStylesheet so that useHead is always called
(unless import.meta.server) — rely on the CALENDLY_CSS_KEY de-duplication
instead; update ensureCalendlyStylesheet (referenced by useScriptCalendly) to
only skip on import.meta.server and then call useHead({ style: [{ key:
CALENDLY_CSS_KEY, innerHTML: CALENDLY_CSS }] }) so unhead lifecycle can
re-inject the stylesheet correctly after SPA navigation.
---
Nitpick comments:
In `@packages/script/src/runtime/components/ScriptCalendlyInlineWidget.vue`:
- Around line 10-31: The three local interfaces (CalendlyPrefill, CalendlyUtm,
CalendlyPageSettings) in ScriptCalendlyInlineWidget.vue duplicate types defined
in calendly.ts and introduce a missing field (hideGdprBanner) divergence; fix by
exporting the canonical interfaces from calendly.ts (add hideGdprBanner to
CalendlyPageSettings there), then remove the local redeclarations and import
CalendlyPrefill, CalendlyUtm, and CalendlyPageSettings into
ScriptCalendlyInlineWidget.vue so useScriptCalendly/initInlineWidget consumers
have a single, type-safe source of truth.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6e92d3c9-5b4c-4a1c-be3c-b56a9a0137bc
📒 Files selected for processing (5)
docs/content/scripts/calendly.mdpackages/script/src/runtime/components/ScriptCalendlyInlineWidget.vuepackages/script/src/runtime/registry/calendly.tsplayground/pages/third-parties/calendly/nuxt-scripts.vuetest/e2e/_calendly-suite.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/content/scripts/calendly.md
- test/e2e/_calendly-suite.ts
| watch(status, (s) => { | ||
| if (s === 'error') | ||
| emit('error') | ||
| }) |
There was a problem hiding this comment.
watch on status registered with default immediate: false may miss a pre-existing error state.
If the script context entered 'error' before this component mounts (e.g., a shared useRegistryScript instance that already failed on a previous navigation), the error event is never emitted because watch doesn't fire for the value present at registration time.
🐛 Proposed fix
- watch(status, (s) => {
+ watch(status, (s) => {
if (s === 'error')
emit('error')
- })
+ }, { immediate: true })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| watch(status, (s) => { | |
| if (s === 'error') | |
| emit('error') | |
| }) | |
| watch(status, (s) => { | |
| if (s === 'error') | |
| emit('error') | |
| }, { immediate: true }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/script/src/runtime/components/ScriptCalendlyInlineWidget.vue` around
lines 92 - 95, The watch on status (watch(status, (s) => { if (s === 'error')
emit('error') })) can miss a pre-existing error because it registers with the
default immediate: false; update the component so that the current status is
checked at registration by either adding the watch option { immediate: true } or
explicitly checking if status === 'error' and calling emit('error') during
setup/mount; reference the existing watch(status, ...) and the emit('error')
call when making the change.
| interface CalendlyPageSettings { | ||
| backgroundColor?: string | ||
| hideEventTypeDetails?: boolean | ||
| hideLandingPageDetails?: boolean | ||
| primaryColor?: string | ||
| textColor?: string | ||
| } |
There was a problem hiding this comment.
CalendlyPageSettings is missing hideGdprBanner.
Calendly's SDK accepts hideGdprBanner as a page setting. It is absent here but present in ScriptCalendlyInlineWidget.vue's local copy of the same interface (causing the type divergence noted in the component review). Users of useScriptCalendly composing CalendlyInlineWidgetOptions or CalendlyPopupWidgetOptions directly cannot pass this field in a type-safe way.
🐛 Proposed fix
interface CalendlyPageSettings {
backgroundColor?: string
hideEventTypeDetails?: boolean
hideLandingPageDetails?: boolean
+ hideGdprBanner?: boolean
primaryColor?: string
textColor?: string
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| interface CalendlyPageSettings { | |
| backgroundColor?: string | |
| hideEventTypeDetails?: boolean | |
| hideLandingPageDetails?: boolean | |
| primaryColor?: string | |
| textColor?: string | |
| } | |
| interface CalendlyPageSettings { | |
| backgroundColor?: string | |
| hideEventTypeDetails?: boolean | |
| hideLandingPageDetails?: boolean | |
| hideGdprBanner?: boolean | |
| primaryColor?: string | |
| textColor?: string | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/script/src/runtime/registry/calendly.ts` around lines 26 - 32, The
CalendlyPageSettings interface is missing the hideGdprBanner property which
causes a type mismatch with ScriptCalendlyInlineWidget.vue and prevents callers
of useScriptCalendly (and types CalendlyInlineWidgetOptions /
CalendlyPopupWidgetOptions that compose CalendlyPageSettings) from passing this
flag type-safely; update the CalendlyPageSettings interface to include
hideGdprBanner?: boolean so the exported types align with the local copy in
ScriptCalendlyInlineWidget.vue and downstream option types accept the field.
| let cssInjected = false | ||
|
|
||
| function ensureCalendlyStylesheet() { | ||
| if (import.meta.server || cssInjected) | ||
| return | ||
| cssInjected = true | ||
| useHead({ | ||
| style: [ | ||
| { | ||
| key: CALENDLY_CSS_KEY, | ||
| innerHTML: CALENDLY_CSS, | ||
| }, | ||
| ], | ||
| }) | ||
| } |
There was a problem hiding this comment.
cssInjected module-level flag causes the Calendly stylesheet to disappear after SPA navigation.
When a component is unmounted, any head entries created by that component are automatically removed by unhead. Because ensureCalendlyStylesheet() is called inside useScriptCalendly() during component setup(), the useHead entry is component-scoped. On the first visit to a Calendly page the style is injected and cssInjected flips to true. When the user navigates away, the component unmounts and unhead removes the <style> tag. On the next visit, ensureCalendlyStylesheet() returns early because cssInjected is still true — the style is never re-injected, leaving the widget unstyled.
The fix is to drop the module-level flag entirely and let unhead's key handle deduplication. Multiple co-existing components on the same page calling useHead with the same key are safely collapsed to one <style> tag; when any one of them mounts or unmounts, the entry lifecycle follows naturally.
🐛 Proposed fix
-let cssInjected = false
-
function ensureCalendlyStylesheet() {
- if (import.meta.server || cssInjected)
+ if (import.meta.server)
return
- cssInjected = true
useHead({
style: [
{
key: CALENDLY_CSS_KEY,
innerHTML: CALENDLY_CSS,
},
],
})
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/script/src/runtime/registry/calendly.ts` around lines 89 - 103,
Remove the module-level boolean cssInjected and its short-circuit in
ensureCalendlyStylesheet so that useHead is always called (unless
import.meta.server) — rely on the CALENDLY_CSS_KEY de-duplication instead;
update ensureCalendlyStylesheet (referenced by useScriptCalendly) to only skip
on import.meta.server and then call useHead({ style: [{ key: CALENDLY_CSS_KEY,
innerHTML: CALENDLY_CSS }] }) so unhead lifecycle can re-inject the stylesheet
correctly after SPA navigation.
🔗 Linked issue
Related to #177
❓ Type of change
📚 Description
Adds Calendly to the registry as
useScriptCalendlyplus a headless<ScriptCalendlyInlineWidget>component for the most common embed shape. Bundled and proxied viaassets.calendly.comwithPRIVACY_IP_ONLY; the booking iframe still hitscalendly.comdirectly since the vendor frame must load from origin.🐞 Stylesheet leak fix (the bug analogous to #751's beacon-origin regression)
The composable used to inject
<link rel=stylesheet href=https://assets.calendly.com/assets/external/widget.css>which leaked the visitor's IP to the vendor on every page render — and theurl(/assets/external/close-icon.svg)reference inside that stylesheet leaked again on every popup close. The original e2e suite encoded this as expected behaviour (waitForSelector('link[href*="assets.calendly.com/assets/external/widget.css"]')), so the regression would have been invisible.Fix: inline the 2.4 KB stylesheet via
useHead({ style }), with the close-icon SVG embedded as a data URI. Verified live againsthttps://calendly.com/<user>/30min: zero requests toassets.calendly.comin any of the three modes (inline / popup / badge).🧪 Usage
Inline (component)
The component lazy-loads on visibility by default, exposes
loading/awaitingLoad/errorslots, and acceptsprefill/utm/pageSettings/aboveTheFold/minHeight/triggerprops. Mirrors<ScriptYouTubePlayer>.Inline (composable)
Popup
Badge
🤔 Why a component for inline only?
Script*.vuecomponents encode (cf. YouTube, Stripe, Vimeo). Without it consumers re-implementref + onMounted + initInlineWidget({parentElement})and get the SSR/trigger/cleanup boundaries wrong.body, badge floats fixed. Wrapping a singleonMountedcall would add no value, so they stay composable-only.✅ What's covered
https://calendly.com/<user>/30min); all three modes render in a real browser; zero requests toassets.calendly.comconfirmed viaperformance.getEntriesByType('resource')./_scripts/assets/<hash>.js(bypassing browser route stubs) and asserts the artefact exportsinitInlineWidget/initPopupWidget/initBadgeWidgetand contains zeroassets.calendly.comsubstrings — so any future regression that re-introduces the leak fails the suite.assets.calendly.comis present and that the inline<style>carries both.calendly-spinnerrules and adata:image/svg+xmlclose-icon URI.#calendly-hostwith the user's URL andembed_type=Inline.proxy-configsunit test rows andtypes.test-dentry both updated. Full suite (51 files, 793 tests) green.