Files
cal/app/vite.embed.config.ts
zooqueen 917a88c35b embed the booker in any page, named by a key, measured as a funnel
A host page gets a booker in two lines: a [data-hanzo-cal] element carrying a
pk_ key and one ~2 kB classic script. Add [data-hanzo-cal-popup] and the same
key opens it over the page instead.

The key is a public selector, not a credential: pk_ + base64url(host/eventSlug),
the same pair the /{host}/{eventSlug} URL already names, so it resolves with no
lookup and no registry and the public config still comes from the event-type
endpoint <Booker> was always calling. Base reserves the hyphenated pk- for
read-only API credentials; this is a different kind of value and is never sent
as one.

Each booker is a frame at the new /embed route, which is what keeps a host's CSS
out — reusing the same <Booker> and the same /v1/calendar calls as the full-page
flow, which is untouched. What a frame cannot do is size itself, so /embed posts
its height and the loader applies it; the same channel carries close, because
Escape pressed inside a frame never reaches the page around it.

Every surface that shows a booker now reports reaching a visitor and creating a
booking, distinguished by one `surface` property: page, hero, inline, popup.
@hanzo/event is the fleet's client tracker, and its taxonomy is closed on
purpose — a dimension is a property, never a new event name — so the funnel
rides the conversion names qualified by kind:'booking', which also keeps free
intro calls out of the Sale goal (order_completed{kind:'plan'}). A key that
names no calendar is not a funnel step but a host misconfiguration, so it goes
to the error plane where someone is watching.

Only the two public identifiers ride along. Never the booking uid: that is the
capability that reveals the meeting location and cancels the booking. DNT and
GPC are honoured because the SDK reads neither, and a booker sitting in someone
else's page should be the polite one. Interior steps stay unreported —
<Booker>'s only lifecycle prop is onBooked, and slot_selected and
details_entered live in its private state machine; reaching them means one prop
upstream in hanzoai/gui, not a fork of the UI here.

hanzoai/spa refuses framing by default, so the image now sets ALLOW_FRAMING.
tsconfig repeats the Vite alias for hanzogui, without which the typecheck this
adds cannot resolve @hanzo/booker's peer.
2026-08-13 10:50:31 -07:00

20 lines
565 B
TypeScript

import { defineConfig } from "vite";
// The host-page loader ships as one classic script at a stable URL, /embed.js,
// so it builds on its own: no React, no hashed chunk, nothing a third party has
// to know the name of. It lands in the SPA's dist, so it runs after the SPA
// build and must not empty the directory.
export default defineConfig({
build: {
outDir: "dist",
emptyOutDir: false,
target: "es2018",
lib: {
entry: "src/embed.ts",
formats: ["iife"],
name: "HanzoCal",
fileName: () => "embed.js",
},
},
});