Before your widget’s HTML loads, the native app installs one global object,
window.PREEN. It’s the entire interface between your widget and the hub. There
is no other bridge — no network, no token, no imports.
Methods
| Method | Purpose |
|---|---|
onData(cb) | Register your renderer. cb(payload) runs on every data update. If data already arrived, it fires immediately with the last payload. |
ready() | Signal the UI is hydrated (flips the loading card to “live”). Optional — onData delivery calls it for you. |
trigger(actionId, args?) | Fire a pre-registered action (interactive widgets). args is an optional JSON object. |
pulse(detail?) | Chirp this phone’s bird on the Mac — each paired device has its own bird in the hub’s device strip. Side-effect-free; works in release builds. |
db | The widget’s own durable datastore on the Mac — db.put / get / update / delete / clear. Each returns a Promise. Works in release builds. |
Signatures
// Data in
PREEN.onData(cb: (payload: any) => void): void
PREEN.ready(): void
// Signals out (post over the native bridge — not network)
PREEN.trigger(actionId: string, args?: object): void // gated by user consent, not build type
PREEN.pulse(detail?: object): void // all builds
// Per-widget datastore (durable SQLite on the Mac) — all return Promises
type Record = { id: number; ts: number; body: any }
PREEN.db.put(collection: string, body: object): Promise<{ id: number; ts: number }>
PREEN.db.get(collection: string, query?: { limit?: number; since?: number; order?: "asc" | "desc" }): Promise<Record[]>
PREEN.db.update(collection: string, id: number, body: object): Promise<{ id: number; ts: number }>
PREEN.db.delete(collection: string, id: number): Promise<{ deleted: number }>
PREEN.db.clear(collection: string): Promise<{ deleted: number }>
Datastore limits: collection names match [A-Za-z0-9_-], ≤64 chars; get
returns 100 rows by default (max 1000); 5,000 rows per collection.
Receiving data
onData is all most widgets need. The payload is exactly the JSON last pushed to
the widget (via push_data or its data endpoint).
window.PREEN.onData((d) => {
d = d || {};
if (d.available === false) return; // a metric may be unavailable on some Macs
render(d);
});
window.PREEN.ready();
Delivery semantics:
- The callback fires once per update, with the full payload (not a diff).
- It replays the last payload if you register
onDataafter data has already arrived — so you never miss the first value, regardless of script timing. - Data is passed as a structured argument, never string-interpolated, so payloads can’t break out into code.
Sending signals out
Two outbound calls exist. Both post over a native message-handler bridge — not
the network — so the connect-src 'none' CSP still holds.
// Interactive widgets: invoke a named action registered against this widget.
button.addEventListener("click", () => window.PREEN.trigger("mutetoggle"));
window.PREEN.trigger("volset", { vol: 40 }); // with arguments
// Any widget: a calm echo back to the Mac (chirps this phone's bird there).
window.PREEN.pulse({ count: 3 });
trigger only fires actions registered against the widget’s own id, and only
once you’ve approved that widget’s capabilities on the Mac — see
Interactive actions. It’s a no-op until then, but works in
every build (release included); pulse always works.
Storing data
onData gives you a single, last-value feed pushed to you. When a widget needs
to remember things across reloads — a high score, a history of runs, anything
it computes itself — use its datastore, window.PREEN.db. Each
widget gets its own SQLite database on the Mac; the calls are async and return
Promises:
// Append a record, then read recent ones back.
await window.PREEN.db.put("sessions", { dur: 5400, end: Date.now() });
const recent = await window.PREEN.db.get("sessions", { limit: 30 });
const best = Math.max(0, ...recent.map((r) => r.body.dur));
Unlike localStorage (which is unreliable in the widget sandbox), the datastore
is durable, queryable, and survives reloads. It works in release builds. See
Widget datastore for the full API.
Reserved internals — don’t touch
The runtime uses these on the object; treat them as private:
__cb, __last, __deliver, __dbReq, __dbPending, __dbResolve.
Your widget should only ever call the public methods above.