A widget is a single, self-contained HTML document. No build step, no bundler, no external files — everything (markup, CSS, JS) lives in one string you hand to the hub. The phone loads it fullscreen and feeds it data.
The minimum
<!doctype html>
<html lang="en">
<head><meta charset="utf-8" /></head>
<body class="preen-glow">
<div class="preen-safe">
<div class="preen-content"><!-- your UI --></div>
</div>
<script>
window.PREEN.onData((d) => render(d));
window.PREEN.ready();
</script>
</body>
</html>
Three things are doing work here:
window.PREEN.onData(cb)— your renderer; called with the latest JSON on every update. See The window.PREEN API.window.PREEN.ready()— tells the runtime the UI is hydrated.- The
preen-*classes — layout + brand helpers the runtime injects. See Theming.
The sandbox (what you can’t do)
Before your code runs, the runtime injects this Content-Security-Policy as a
<meta> tag:
default-src 'none'; connect-src 'none';
img-src data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'; font-src data:
Consequences:
- No network.
fetch,XMLHttpRequest, WebSockets, and beacon requests are blocked byconnect-src 'none'. Data only ever arrives throughonData. - No external assets. Images must be
data:URIs; fonts must bedata:or system fonts. Inline<style>and<script>are allowed. - You may add your own
<meta>CSP, but it can only further restrict — CSP policies intersect.
This is the D7 rule: a widget is a pure function of injected data, holds no token, and does no I/O. The native app does all networking on your behalf.
What the runtime provides
| Provided | What it is |
|---|---|
window.PREEN | The data + signals bridge (reference). |
--preen-* CSS variables | Brand colors + safe-area insets (theming). |
preen-safe, preen-display, preen-content, preen-glow | Layout + glow helper classes. |
preen-landscape / preen-portrait | Orientation classes toggled on <body> automatically. |
Authoring tip
You rarely write widget HTML by hand — you describe it to Claude Code and it
calls publish_widget. But knowing the contract above lets you review what it
produces and debug when a widget shows — instead of data.