@marktiderman/genesis-widgets-web
v0.1.1
Published
Genesis Widget Platform browser loader + embeddable feedback widget. A small, purpose-built vanilla-DOM runtime that renders inside a Shadow DOM (no React/Radix). Ships an ESM npm entry and a self-contained CDN loader (PRD-09).
Readme
@marktiderman/genesis-widgets-web
Browser loader + embeddable feedback widget for the Genesis Widget Platform (PRD-09). A small, purpose-built vanilla-DOM runtime that renders inside a Shadow DOM — no React, Radix, or shadcn in the embedded path.
Install (CDN <script>)
One line on the host page. The loader is a CDN-only <script> artifact
(dist/loader.js, a self-contained IIFE that assigns window.Genesis) — it is
not an importable module and is intentionally not exposed as a package
subpath. Point a <script src> at it:
<script
src="https://cdn.your-genesis-host/loader.js"
data-genesis-key="pk_live_xxxxxxxx"
data-genesis-endpoint="https://<ref>.supabase.co"
></script>The loader reads its own <script> tag for the publishable key (data-genesis-key)
and an optional endpoint override (data-genesis-endpoint), persists an anonymous
device id in localStorage, runs the boot handshake, and mounts a floating
launcher for each enabled widget.
The publishable key (
pk_…) is an identifier, not a secret — it is origin-locked at the edge.
Signed identity
For projects in signed identity mode, mint a short-lived JWT server-side and
hand it to the loader after it boots:
<script>
// after your app knows who the user is:
window.Genesis.identify(signedJwtFromYourServer);
</script>window.Genesis exposes:
window.Genesis = {
identify(jwt: string): void; // attach a signed JWT; remounts as `signed`
boot(): Promise<void>; // re-run boot + remount
destroy(): void; // tear everything down
};Programmatic mount (React / Next / Vite)
The typed alternative to the <script> tag — adds no React dependency (it calls
the same vanilla shell). A real <GenesisWidgets> React component wrapper is a
later phase.
import { initGenesisWidgets } from "@marktiderman/genesis-widgets-web";
const widgets = initGenesisWidgets({
apiKey: "pk_live_xxxxxxxx",
endpoint: "https://<ref>.supabase.co",
// jwt: signedJwt, // optional, for `signed` identity
});
// later
widgets.identify(signedJwt);
widgets.destroy();Shadow-DOM isolation
The launcher + panel are built with plain document.createElement inside an
open shadow root. This is deliberate: React/Radix/shadcn overlays portal to
document.body, which lands outside the shadow root and breaks focus-trap,
scroll-lock, and keyboard nav in an embed.
- Styles are injected as a single
<style>inside the shadow root — nothing bleeds in from the host page, nothing bleeds out. - Theme comes from
BootConfig.themetokens applied as CSS custom properties on the shadow host (:host { --gw-color-primary: … }), with:host { all: initial }blocking inherited host-page styles. No hard-coded palette. - No content is portaled into the host document — the entire UI, including the modal dialog and its focus management, lives in the shadow root.
Bundle budget & build safeguards
The CDN loader.js is a self-contained IIFE (the shared core is bundled in, since
a <script> tag has no module resolver). Budget: ≤ 15 KB gzip (PRD-09 §A3).
Keep this path vanilla-DOM — pulling React/Radix in here would blow the budget and
break the isolation guarantee above.
Two zero-dependency Node scripts enforce this after a build:
pnpm --filter @marktiderman/genesis-widgets-web build
pnpm --filter @marktiderman/genesis-widgets-web size # gzip(dist/loader.js) ≤ 15 KB, else non-zero exit
pnpm --filter @marktiderman/genesis-widgets-web scan:secrets # no service_role / signing-secret markers in dist/*.jssize(scripts/check-loader-size.mjs) — gzipsdist/loader.jsand fails over the 15 KB budget.scan:secrets(scripts/scan-bundle-secrets.mjs) — fails if any builtdist/*.jscontains a forbidden secret marker (service_role,SUPABASE_SERVICE_ROLE,signing_secret,SB_SECRET_KEY). The widget ships no secret — only the publishable key — so this is a proof, not a filter.
Package entries
| Entry | Format | Notes |
| ----------------------------------- | -------------- | --------------------------------------------------------------------------- |
| @marktiderman/genesis-widgets-web | ESM, .d.ts | npm entry (the . export); core kept external; tree-shakeable. |
| dist/loader.js | IIFE, minified | CDN <script src> only — not an import subpath; core bundled in; sets window.Genesis. |
