Conversation UI widget sandbox

Agent connection idle

The Project ID becomes the JWT iss; the key signs it in-browser (ES256). The key is kept in this browser's localStorage only — never sent anywhere, never committed. Locally it falls back to /dev-private.pem, which the deployed build does not ship. Project ID is also settable as ?projectId=, and retrieval as ?retrievalMode=vector|pageindex — the switch the answer-quality suite flips to score one strategy against the other. Connect remounts the widget against these values; starting a conversation and opening or closing the panel are the FAB's job, and ending one is the panel's End button. Every minted token's claims are logged to the console, since a 403 from the backend doesn't say whether it rejected the claims or the signature.

The signing key is optional. The project decides whether a JWT is needed: one whose auth_mode is origin connects without one from this page (, the widget's own origin, which the origin tier always accepts), and nothing is minted — those conversations are recorded as preview, not live. Any other project needs the key.

Appearance (applied on reload — the load-time install model)

These are the install snippet's data-* attributes, and they OUTRANK the project's stored appearance. Clear a field (or tick “use project colour”) to send nothing for it and see what the project's own config paints — which only works against a deploy, since pnpm dev runs no Pages Function. FAB size and corner radius are the exceptions: the loader applies both to the iframes it owns in this page, so they stay install-only and a project value for them is ignored. Every field is also settable as a query param under its real wire name — this page owns only its connection params and forwards every other one as appearance — so a field added to the registry before a control is added here is still reachable, and Reset clears it.