@wyndlass/app-sdk 1.1.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,12 +61,14 @@ Its controls:
61
61
  - **The clock:** play, pause, seek, and speeds from 0.25× to 4×.
62
62
  - **Enter and exit,** with each preset and how long it lasts, and the app's pages.
63
63
  - **Data:** each data source's answer, from samples you can edit in the page. `--data <dir>` reads `<source>.json` there instead (the answer's `data`, or a whole answer). The preview reaches no data service. A source the definition doesn't declare gets no answer, as on a screen.
64
- - **Tables:** each file in `<dir>/tables/`, or one you add in the page. A file may be CSV, a JSON array of rows, or a snapshot. A CSV's header gives its columns' labels, and their keys are made as Wyndlass makes a table's (`Now serving` is `now_serving`), so a `column` param holds the key it will on screens. A `datatable` param chooses among the tables, a `column` param among the chosen table's columns, and an app with `slots` is bound to the first table.
64
+ - **Tables:** each file in `<dir>/tables/`, or one you add in the page. A file may be CSV, a JSON array of rows, or a snapshot. A CSV's header gives its columns' labels, and their keys are made as Wyndlass makes a table's (`Now serving` is `now_serving`), so a `column` param holds the key it will on screens. A `datatable` param chooses among the tables, a `column` param among the chosen table's columns, and an app with `slots` is bound to the first table. Edit a table's rows in the page, or change its file, and the app gets them as it plays, as a screen gives a table's new version: frozen, and without making the app again. The report says how many times the app was made.
65
65
  - **The people at the screen,** for an app that declares the `impressions` sensor: someone arrives, looks, or leaves.
66
66
  - **The player's controller,** for what `capabilities.player` allows. The page sends the app the player's events, each one editable. A command the app may give is answered "not applied", and one it may not is refused, as on a screen.
67
67
 
68
68
  **What a screen would report** is shown beside the app: how long it took to say it was ready, or that it was late, and each failure with its reason.
69
69
 
70
+ **What it makes while it plays** is shown below that: anything it makes at every frame that holds memory outside the script's heap, such as date and number formatters (`Intl.DateTimeFormat`, or `toLocaleTimeString` with options), canvases, object URLs, workers and listeners on `window`, with what to do; and how many DOM nodes it has. One made at every frame holds memory a screen frees late, and can run it out. Fix what it says before you pack.
71
+
70
72
  **`npm run preview -- --frame`** runs the app in the frame the CMS gives every imported app: sandboxed, on an origin of its own, with no storage, cookies or parent page. An app that reaches for one of them works on screens but fails in the CMS's designer. Run it with `--frame` before you import.
71
73
 
72
74
  ## Check and pack it
@@ -147,6 +149,7 @@ export default class QueueBoard extends WyndlassApp {
147
149
  - `kept(this, source, params)`, the answer the screen last kept, at once;
148
150
  - `image(this, path)`, `snapshot(this, table)`, `controllerOf(this)` and `impressionsOf(this)`;
149
151
  - `contextOf(this)`, for the rest.
152
+ - **A table's rows change while the app plays.** A screen gives the app a table's new rows within about a second of the save, without making it again. Read them where you draw: `snapshot()` in `draw()`, `useSnapshot`, or `tableSignal`. Never read them once in `mount()` and keep them. On a screen they are the same object until they change, and frozen: copy rows before you sort or change them.
150
153
  - **Helpers:** `fit`, `typographyCss`, `color` and `escape`, and the presets' curves `easingOf`, `cubicBezier` and `staggerTimes`.
151
154
  - **Types:**
152
155
  - the contract's (`AppContext`, `DataAnswer`, `HookOptions` and the rest);
@@ -165,7 +168,7 @@ import css from "./app.css";
165
168
 
166
169
  function Board() {
167
170
  const app = useApp();
168
- const now = useNow();
171
+ const now = useNow(1000);
169
172
  const page = usePaging(2, 8000);
170
173
  const { answer, error } = useData("weather", { lat: 49.9, lon: -97.14 });
171
174
  useEffect(() => { if (answer || error) app.ready(); }, [app, answer, error]);
@@ -177,13 +180,38 @@ export default reactApp(Board, { css });
177
180
 
178
181
  - **`reactApp(Component, { css })`** makes the element's class, the module's default export. The component renders in the app's shadow root, with `css` as its styles. React is a peer dependency, and `pack` bundles it.
179
182
  - **`useApp()`:** `prop(name, default)`, `ready()`, `timeZone`, `locale`, the screen's `place` and `screen`, and `preview`.
180
- - **`useNow()`:** the layout's clock, drawn again at each of its frames. The component renders at each one, many times a second: make formatters (`Intl.DateTimeFormat`, `Intl.NumberFormat`) and other objects with `useMemo`, never in the render. One made in each draw holds memory a screen does not free in time: an app that did so ran a 4 GB screen out of memory within an hour.
181
- - **`usePaging(count, ms)`:** the page showing now. It also tells the host the pages.
183
+ - **`useNow(everyMs?)`:** the layout's clock. Without `everyMs`, the component renders again at each of the layout's frames, many times a second. With it, the time is a multiple of `everyMs` (`useNow(1000)`: whole seconds), and the component renders only when that changes. Take the coarsest the app shows. Either way, make formatters (`Intl.DateTimeFormat`, `Intl.NumberFormat`) and other objects with `useMemo`, never in the render. One made in each draw holds memory a screen does not free in time: an app that did so ran a 4 GB screen out of memory within an hour.
184
+ - **`usePaging(count, ms)`:** the page showing now, rendering when it turns. It also tells the host the pages.
182
185
  - **`useData(source, params)`:** at once the answer the screen kept, then the service's, asked again every 10 minutes. `error` is set once an ask fails with nothing to show.
183
- - **`useSnapshot(table)`:** a data table's snapshot.
186
+ - **`useSnapshot(table)`:** a data table's snapshot, as `snapshot` gives it. The component renders when the rows change: when the screen gets a table's new version, or when a binding's rows, chosen on the layout's clock, change. On players before Wyndlass's live rows (WP-0149), which make the app again when a table changes, it renders at every frame, as before.
184
187
 
185
188
  The hooks reach the app's context through the SDK's own module, never through React's tree, so nothing another app could use hangs off the shadow root.
186
189
 
190
+ ## Signals (`@wyndlass/app-sdk/signals`)
191
+
192
+ For an app that updates its DOM when its rows change, rather than at every frame:
193
+
194
+ ```ts
195
+ import { WyndlassApp } from "@wyndlass/app-sdk";
196
+ import { effect, tableSignal } from "@wyndlass/app-sdk/signals";
197
+
198
+ export default class Queue extends WyndlassApp {
199
+ protected mount() {
200
+ const rows = tableSignal(this, String(this.prop("table", "")));
201
+ const number = document.createElement("p");
202
+ this.root.append(number);
203
+ effect(() => { number.textContent = String((rows.value as { rows?: { number?: string }[] } | null)?.rows?.[0]?.number ?? ""); });
204
+ this.ready();
205
+ }
206
+ protected draw() { /* the effect draws */ }
207
+ }
208
+ ```
209
+
210
+ - **`tableSignal(this, table)`:** a read-only [Preact signal](https://github.com/preactjs/signals) of the table's snapshot. Its value changes when the rows do, and is the same object until then. Make it in `mount()`.
211
+ - **`effect`, `computed`, `batch`, `signal`, `untracked`:** Preact's, from the same copy. Import them from here, not from `@preact/signals-core`: an effect from another copy does not see these signals.
212
+ - **Its own entry,** so an app that doesn't import it doesn't bundle it (about 2 KB gzipped when it does).
213
+ - On players before WP-0149, the signal keeps the rows the app was made with: such a player makes the app again when a table changes.
214
+
187
215
  ## Limits
188
216
 
189
217
  - The zip: at most 6 MiB, holding only `app.json` (at most 256 KiB) and `app.js` (at most 5 MiB).
package/cli/preview.mjs CHANGED
@@ -84,6 +84,10 @@ export async function preview(dir = ".", { port = 5199, data: dataDir, frame = f
84
84
  await firstBuilt;
85
85
  // The definition is not a source esbuild follows.
86
86
  const definitionWatch = watch(join(root, "app.json"), () => { version++; tell(); });
87
+ // The data folder's tables (WP-0149): a change is given to the app as it plays, not by drawing it again.
88
+ const tablesDir = dataDir && join(resolve(dataDir), "tables");
89
+ const tablesWatch = tablesDir && existsSync(tablesDir) ? watch(tablesDir, () => { for (const c of clients) c.write("event: tables\ndata: {}\n\n"); }) : null;
90
+ const unwatch = () => { definitionWatch.close(); tablesWatch?.close(); };
87
91
 
88
92
  const server = createServer((req, res) => {
89
93
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -113,26 +117,26 @@ export async function preview(dir = ".", { port = 5199, data: dataDir, frame = f
113
117
  }
114
118
  });
115
119
  await new Promise((ok, fail) => { server.once("error", fail); server.listen(port, "127.0.0.1", ok); })
116
- .catch(e => { void builder.dispose(); definitionWatch.close(); throw new CliError(e.code === "EADDRINUSE" ? `port ${port} is taken: give another with --port` : e.message); });
120
+ .catch(e => { void builder.dispose(); unwatch(); throw new CliError(e.code === "EADDRINUSE" ? `port ${port} is taken: give another with --port` : e.message); });
117
121
  const { address, port: bound } = server.address();
118
122
  pageOrigin = `http://${address}:${bound}`;
119
123
  if (frame) {
120
124
  // The frame's page and runtime only, nothing else, under the frames' policy: the app's document there is opaque.
121
125
  frames = createServer((req, res) => {
122
126
  const path = new URL(req.url ?? "/", "http://127.0.0.1").pathname;
123
- const file = path === "/" ? "index.html" : path === "/frame.js" ? "frame.js" : null;
127
+ const file = path === "/" ? "index.html" : path === "/frame.js" ? "frame.js" : path === "/made.js" ? "made.js" : null;
124
128
  if (!file) return res.writeHead(404).end();
125
129
  res.writeHead(200, { "content-type": TYPES[file.endsWith(".js") ? ".js" : ".html"], "cache-control": "no-store", "content-security-policy": framePolicy(framesOrigin, pageOrigin) })
126
130
  .end(readFileSync(join(page, "frame", file)));
127
131
  });
128
132
  await new Promise((ok, fail) => { frames.once("error", fail); frames.listen(port === 0 ? 0 : port + 1, "127.0.0.1", ok); })
129
- .catch(async e => { await builder.dispose(); definitionWatch.close(); server.close(); throw new CliError(e.code === "EADDRINUSE" ? `port ${port + 1}, for the frames, is taken: give another with --port` : e.message); });
133
+ .catch(async e => { await builder.dispose(); unwatch(); server.close(); throw new CliError(e.code === "EADDRINUSE" ? `port ${port + 1}, for the frames, is taken: give another with --port` : e.message); });
130
134
  framesOrigin = `http://127.0.0.1:${frames.address().port}`;
131
135
  }
132
136
  return {
133
137
  url: `http://${address}:${bound}/`, address, port: bound, frames: framesOrigin || null,
134
138
  async close() {
135
- definitionWatch.close();
139
+ unwatch();
136
140
  if (frames) await new Promise(r => frames.close(r));
137
141
  for (const c of clients) c.end();
138
142
  await builder.dispose();
@@ -178,9 +178,17 @@ export interface AppContext {
178
178
  binding?: TableBinding;
179
179
  /**
180
180
  * A data table's snapshot, as the manifest carries it; null without one. The bound table's comes with only the rows the
181
- * binding chooses, chosen at each read on the layout's clock with the screen's facts (WP-0137).
181
+ * binding chooses, chosen at each read on the layout's clock with the screen's facts (WP-0137). On a screen it is the same
182
+ * object until the table's next version, and frozen (WP-0149): copy rows before changing them.
182
183
  */
183
184
  snapshot(table: string): unknown | null;
185
+ /**
186
+ * Tells the app that a table's snapshot changed while it plays (WP-0149): the listener gets the table's id once
187
+ * snapshot() gives the new rows, before the next frame. Props with tokens are filled again by then. Returns what stops
188
+ * it; the host stops them all when the app goes. Absent where rows never change while an app runs, and on players
189
+ * older than WP-0149 part a, so apps check for it.
190
+ */
191
+ onSnapshot?(listener: (table: string) => void): () => void;
184
192
  /** The app's view of the player's controller, on screens (WP-0100); absent in the designer and previews. */
185
193
  controller?: AppController;
186
194
  /**
@@ -0,0 +1,4 @@
1
+ /** Runs `f` at each frame the app draws, before its draw; returns what stops it. */
2
+ export declare function eachFrame(app: object, f: () => void): () => void;
3
+ /** The app's frame: what runs at it, in turn. */
4
+ export declare function atFrame(app: object): void;
package/dist/frames.js ADDED
@@ -0,0 +1,18 @@
1
+ // What the SDK's own modules run at each of an app's frames, just before it draws (WP-0149): the signals' reads. Not one of
2
+ // the package's exports, so it is no part of what an app may call.
3
+ const work = new WeakMap();
4
+ /** Runs `f` at each frame the app draws, before its draw; returns what stops it. */
5
+ export function eachFrame(app, f) {
6
+ let set = work.get(app);
7
+ if (!set)
8
+ work.set(app, set = new Set());
9
+ set.add(f);
10
+ return () => { set.delete(f); };
11
+ }
12
+ /** The app's frame: what runs at it, in turn. */
13
+ export function atFrame(app) {
14
+ const set = work.get(app);
15
+ if (set)
16
+ for (const f of [...set])
17
+ f();
18
+ }
package/dist/index.d.ts CHANGED
@@ -36,7 +36,9 @@ export declare function image(app: WyndlassApp, path: string): Promise<string>;
36
36
  /**
37
37
  * A data table's snapshot as the manifest carries it: a table the layout names, in a datatable param or in the element's
38
38
  * binding, which needs no binding of its own; the bound table's with only the rows the binding chooses. Null when the
39
- * screen has no snapshot of that table.
39
+ * screen has no snapshot of that table. On a screen it is the same object until the table's rows change, and frozen: copy
40
+ * rows before changing them. Read it where it is drawn (draw(), useSnapshot or tableSignal), never once at mount: a
41
+ * screen gives the app a table's new rows while it plays (WP-0149).
40
42
  */
41
43
  export declare function snapshot(app: WyndlassApp, table: string): unknown | null;
42
44
  /** The app's view of the player's controller (ADR 0050), on screens, as its definition's `capabilities.player` allows. */
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ var _WyndlassApp_instances, _WyndlassApp_said, _WyndlassApp_hooks, _WyndlassApp_
24
24
  */
25
25
  import { ATTACH } from "./contract/app-contract.js";
26
26
  import { cubicBezier, easingOf, staggerTimes } from "./easing.js";
27
+ import { atFrame } from "./frames.js";
27
28
  export { ATTACH };
28
29
  export { cubicBezier, easingOf, staggerTimes } from "./easing.js";
29
30
  /** The contexts handed over through ATTACH, where no other script on the page can reach them. */
@@ -50,7 +51,9 @@ export function image(app, path) {
50
51
  /**
51
52
  * A data table's snapshot as the manifest carries it: a table the layout names, in a datatable param or in the element's
52
53
  * binding, which needs no binding of its own; the bound table's with only the rows the binding chooses. Null when the
53
- * screen has no snapshot of that table.
54
+ * screen has no snapshot of that table. On a screen it is the same object until the table's rows change, and frozen: copy
55
+ * rows before changing them. Read it where it is drawn (draw(), useSnapshot or tableSignal), never once at mount: a
56
+ * screen gives the app a table's new rows while it plays (WP-0149).
54
57
  */
55
58
  export function snapshot(app, table) {
56
59
  return contextOf(app)?.snapshot(table) ?? null;
@@ -125,6 +128,7 @@ export class WyndlassApp extends HTMLElement {
125
128
  update(epochMs) {
126
129
  if (!__classPrivateFieldGet(this, _WyndlassApp_mounted, "f"))
127
130
  return;
131
+ atFrame(this);
128
132
  this.draw(epochMs);
129
133
  __classPrivateFieldGet(this, _WyndlassApp_instances, "m", _WyndlassApp_present).call(this, epochMs);
130
134
  }
@@ -1,2 +1,2 @@
1
1
  /* @wyndlass/app-sdk's preview: player-core's app frame, compiled. MIT, (c) Northwynd Labs LLC. */
2
- "use strict";(()=>{var pt=Object.defineProperty;var lt=(e,t,n)=>t in e?pt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>lt(e,typeof t!="symbol"?t+"":t,n);var ve={apiVersion:2,properties:[{name:"device",description:"What the device is: id, type, shell and versions, model, resolution and orientation. Fixed while the player runs."},{name:"mode",description:"Where player-core runs: player, preview (in the CMS) or designer. In preview and the designer, commands change nothing."},{name:"timeZone",description:"The player's time zone (IANA), from the host.",event:"time.changed"},{name:"locale",description:"The player's locale (BCP 47), from the host."},{name:"connection",description:"Whether the player is connected to Wyndlass.",event:"connection.changed"},{name:"state",description:"starting, playing, paused, stopped (the standby screen), idle (nothing to play) or error.",event:"state.changed"},{name:"held",description:"A remote pause or stop in effect, by whom and since when; absent when none.",event:"state.changed"},{name:"content",description:"The manifest playing, when it was issued and switched to; absent when there is none.",event:"content.updated"},{name:"now",description:"What each zone shows: the item, its blob and content type, when it started, and its duration and position when known.",event:"item.start"},{name:"audio",description:"The player's volume (0 to 100) and whether it is muted; only when the host can set them.",event:"audio.changed"},{name:"display",description:"The display's power and brightness; only when the host can report them.",event:"display.changed"},{name:"capabilities",description:"What the host can do: audio, display power, display brightness."}],events:[{name:"state.changed",description:"The player's state changed (playing, paused, stopped, idle, error), and who changed it."},{name:"content.updated",description:"A new manifest was switched to."},{name:"item.start",description:"An item started in a zone (source start)."},{name:"item.end",description:"An item ended in a zone (source end): completed, skipped, stopped, error, replaced by new content, or finished by its app."},{name:"item.error",description:"An item could not be shown."},{name:"connection.changed",description:"The player connected to Wyndlass or lost its connection."},{name:"audio.changed",description:"The volume or mute changed, and who changed it."},{name:"display.changed",description:"The display's power or brightness changed, and who changed it."},{name:"time.changed",description:"The clock was adjusted or the time zone changed."},{name:"app.command",description:"A command for apps on the screen (apps.sendCommand); an app reacts if it knows the name."},{name:"app.failed",description:"An app on the screen could not run, or misbehaved (WP-0100): its bundle did not load, it was not an app, it needs a newer player, it kept throwing and was stopped, or it was late or overran a clip."},{name:"app.held",description:"An app version was held back on this screen until the next manifest, after the page stopped answering twice in an hour while it was on screen (WP-0100)."}],commands:[{name:"player.status",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/status",description:"Everything about the player now: device, mode, time zone, locale, connection, state (and any hold), the content playing, what each zone shows, audio, display and what the host can do. Changes nothing.",idempotent:!0},{name:"player.events",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/events",result:"player-api-results.schema.json#/$defs/events",description:"The player's recent events (the last 200), oldest first, after a sequence number: state changes, content updates, item starts and ends, errors, connection, audio, display and time. Pass next as after to poll for more. Changes nothing.",idempotent:!0},{name:"time.now",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/timeNow",description:"The player's current time, its time zone and UTC offset, and whether its clock is synchronized. Changes nothing.",idempotent:!0},{name:"player.play",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Resumes or starts playback on the screen. From a remote caller it also ends a remote pause or stop.",idempotent:!0},{name:"player.pause",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Freezes the screen on what it shows now: the loop's timer and any media stop where they are. From a remote caller it holds until a remote player.play or a restart; apps cannot resume it, and new content arrives paused.",idempotent:!0},{name:"player.stop",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Stops playback and shows the standby screen (black, display on). From a remote caller it holds until a remote player.play or a restart; apps cannot restart it, and new content arrives stopped.",idempotent:!0},{name:"player.next",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Skips to the next item in a zone (main by default); the current item ends as skipped."},{name:"player.previous",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Goes back to the previous item in a zone (main by default); the current item ends as skipped."},{name:"player.playItem",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/playItem",result:"player-api-results.schema.json#/$defs/now",description:"Shows a given item of the content playing now in a zone (main by default), then continues the loop from it. Refused for an item the playing manifest does not have."},{name:"audio.setVolume",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setVolume",result:"player-api-results.schema.json#/$defs/audio",description:"Sets the player's volume, 0 to 100. Only on players whose host can set it to a level.",needs:"audio.volume",idempotent:!0},{name:"audio.setMuted",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setMuted",result:"player-api-results.schema.json#/$defs/audio",description:"Mutes or unmutes the player. Only on players whose host can.",needs:"audio",idempotent:!0},{name:"display.setPower",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setPower",result:"player-api-results.schema.json#/$defs/display",description:"Turns the display on or to standby. Only on players whose host can. Standby blanks the screen until it is turned on.",needs:"display.power",idempotent:!0},{name:"display.setBrightness",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setBrightness",result:"player-api-results.schema.json#/$defs/display",description:"Sets the display's brightness, 0 to 100. Only on players whose host can.",needs:"display.brightness",idempotent:!0},{name:"apps.sendCommand",access:"control",apps:"none",remote:!0,params:"player-api-params.schema.json#/$defs/sendCommand",result:"player-api-results.schema.json#/$defs/delivered",description:"Sends a named command, with an optional argument, to the apps on the screen; an app that knows the name reacts. Returns how many apps listen."},{name:"app.finish",access:"control",apps:"read",remote:!1,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/none",description:"An app ends its own slot now: its item ends as finished and the loop moves on. Apps only; not a tool."}],host:[{name:"host.describe",kind:"request",description:"What the device is, its time zone, locale, connection and capabilities.",result:"player-api-host.schema.json#/$defs/describeResult"},{name:"host.time",kind:"request",description:"The host's time and time zone.",result:"player-api-common.schema.json#/$defs/timeNow"},{name:"host.getAudio",kind:"request",description:"The player's volume and mute.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.setAudio",kind:"request",description:"Sets the volume, the mute or both; answers the audio after.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.getDisplay",kind:"request",description:"The display's power and brightness.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.setDisplay",kind:"request",description:"Sets the display's power, brightness or both; answers the display after.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.adRequest",kind:"request",description:"Asks Wyndlass's ad broker for one slot of a break over the host's NATS connection (WP-0075); the result is the broker's ad-decision (ad-decision.schema.json). Only a host whose describe says ads is asked."},{name:"host.mediaPrepare",kind:"request",description:"Lays a video out behind the page and prepares it to its first frame; host.mediaState says when (ADR 0055)."},{name:"host.mediaShow",kind:"request",description:"Shows and hides prepared videos; replies once its layer has changed."},{name:"host.mediaControl",kind:"request",description:"Plays, pauses or seeks a video, or asks for its position."},{name:"host.mediaRelease",kind:"request",description:"Removes a video and frees its decoder."},{name:"host.reply",kind:"reply",description:"The host's answer to a request: result or error (unsupported, failed)."},{name:"host.connection",kind:"event",description:"The connection to Wyndlass changed."},{name:"host.timeChanged",kind:"event",description:"The clock was adjusted or the time zone changed."},{name:"host.audioChanged",kind:"event",description:"The volume or mute changed outside the player."},{name:"host.displayChanged",kind:"event",description:"The display changed outside the player."},{name:"host.mediaState",kind:"event",description:"A video was prepared, started, ended or failed, or its position was asked for."},{name:"host.presence",kind:"event",description:"What one of the host's sensors sees now: the presences in view, with random ids that never leave the page (WP-0105)."},{name:"host.sensing",kind:"event",description:"The site's sensing mode as the host applies it, and its sensors (WP-0105)."},{name:"host.serialPorts",kind:"event",description:"The host's sensor controller ports, and which are open (WP-0043)."},{name:"host.serialLine",kind:"event",description:"One line from a sensor controller, as it arrived (WP-0043)."},{name:"host.serialWrite",kind:"request",description:"Writes lines to a sensor controller, a gap apart; replies once written (WP-0043)."},{name:"host.bleWatch",kind:"request",description:"What the page listens for: beacons' iBeacon UUIDs, Eddystone namespaces and addresses; replies once the scan follows it. Sent only to a host that has sent host.bleState (WP-0044)."},{name:"host.bleAdverts",kind:"event",description:"Beacon frames that match the watch, at most four a second of each kind from each beacon (WP-0044)."},{name:"host.bleState",kind:"event",description:"The host's Bluetooth scanner: scanning, idle, off, refused, missing or failed; sent only by a host that has one (WP-0044)."},{name:"host.gpioPins",kind:"event",description:"The GPIO pins the host reads, with their values (WP-0044)."},{name:"host.gpioEdge",kind:"event",description:"A GPIO pin changed (WP-0044)."},{name:"host.mcp",kind:"relay",description:"An MCP message from ctl.dev.{deviceId}, for player-core to answer."},{name:"core.ready",kind:"core",description:"player-core is ready to answer host.mcp; until then the shell answers -32002 itself."},{name:"core.mcpReply",kind:"core",description:"player-core's answer to a host.mcp, for the shell to send back."},{name:"core.sensors",kind:"core",description:"The page's sensor controllers, channels and inputs, and the beacons it hears, whenever they change, for the heartbeat and sensors.recent (WP-0043, WP-0044)."}]};var ke=Symbol.for("wyndlass.app.attach");var Re=Reflect.apply,re=Object.getOwnPropertyDescriptor,V=Object.getPrototypeOf,y=globalThis,a=(e,t,n)=>Re(e,t,n),v=(e,t)=>e?re(e,t)?.get:void 0,oe=(e,t)=>e?re(e,t)?.set:void 0,f=e=>y[e]?.prototype,Ce=(e,t,n)=>Re(e,t,n);var Cn=Date.now,Ln=new TextEncoder,On=new TextDecoder,jn=TextEncoder.prototype.encode,In=TextDecoder.prototype.decode;var Dn=v(V(Uint8Array.prototype),"length");var ut=Uint8Array,mt=e=>new ut(e),ft=[];for(let e=0;e<256;e++)ft[e]=(e<16?"0":"")+e.toString(16);var ht=Object.create(null);for(let e=0;e<64;e++)ht["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[e]]=e;var Ae=y.crypto?.subtle,se=Ae?V(Ae):void 0,ne=y.crypto,gt=ne?V(ne).getRandomValues:void 0,Hn=se?.digest,Bn=se?.importKey,Wn=se?.sign;var Le=e=>a(gt,ne,[mt(e)]),Un={setTimeout:y.setTimeout,clearTimeout:y.clearTimeout,setInterval:y.setInterval,clearInterval:y.clearInterval};var qn=y.fetch;var B=f("MessagePort"),zn=B&&B.postMessage,Zn=oe(B,"onmessage"),_n=B&&B.start,ie=f("MessageEvent"),Kn=v(ie,"data"),Vn=v(ie,"ports"),Gn=v(ie,"source");var Oe=f("EventTarget"),Jn=Oe?.addEventListener,Yn=Oe?.removeEventListener;var Qn=y.Worker,Z=f("Worker"),Xn=Z&&Z.postMessage,er=oe(Z,"onmessage"),tr=oe(Z,"onerror");var xe=y.Blob,_=y.URL,Se=_?.createObjectURL,yt=_?.revokeObjectURL,ae=xe&&Se?{make:(e,t)=>a(Se,_,[new xe(e,{type:t})]),revoke:e=>a(yt,_,[e])}:void 0,H=y.customElements,de=H?V(H):void 0,bt=de?.define,wt=de?.get,pe=H&&de?{define:(e,t)=>a(bt,H,[e,t]),get:e=>a(wt,H,[e])}:void 0,vt=y.document,K=f("Document"),kt=K?.createElement,nr=K?.createElementNS,At=WeakSet.prototype.add,rr=WeakSet.prototype.has,xt=new WeakSet;var St=(e,t)=>(a(At,e,[t]),t);function je(e){return St(xt,a(kt,vt,[e]))}var W=f("Node"),le=f("Element"),G=f("HTMLElement"),j=e=>v(W,e),Ee=j("parentNode"),Et=j("firstChild"),Tt=j("lastChild"),Mt=j("nextSibling"),Ft=j("nodeType"),Pt=j("childNodes"),$t=v(le,"localName"),Nt=W?.appendChild,Rt=W?.insertBefore,Ct=W?.removeChild,C=le,Lt=C?.getAttribute,Ot=C?.getAttributeNames,jt=C?.setAttribute,It=C?.removeAttribute,Dt=C?.matches,Ht=C?.attachShadow,Bt=C?.querySelectorAll,Wt=f("HTMLDialogElement")?.close,Ut=v(f("NodeList"),"length"),qt=f("NodeList")?.item,J=f("MutationRecord"),zt=v(J,"type"),Zt=v(J,"target"),_t=v(J,"addedNodes"),Kt=v(J,"removedNodes"),Te=y.MutationObserver,Vt=f("MutationObserver")?.observe,Ie=K?re(K,"adoptedStyleSheets"):void 0,Me=Ie?.get,Fe=Ie?.set,Gt=y.requestAnimationFrame,Pe=v(G,"style"),Y=f("CSSStyleDeclaration"),Jt=Y?.getPropertyValue,Yt=Y?.getPropertyPriority,Qt=Y?.setProperty,$e=G?.showPopover,Ne=G?.hidePopover,Xt=v(f("Event"),"target"),en=f("Event")?.stopImmediatePropagation,z=e=>{let t=[];for(let n=0,r=a(Ut,e,[]);n<r;n++)t[n]=a(qt,e,[n]);return t},De=Te&&W&&le&&G&&Y?{parent:e=>a(Ee,e,[]),first:e=>a(Et,e,[]),last:e=>a(Tt,e,[]),next:e=>a(Mt,e,[]),isElement:e=>a(Ft,e,[])===1,name:e=>a($t,e,[]),children:e=>z(a(Pt,e,[])),append:(e,t)=>a(Nt,e,[t]),insert:(e,t,n)=>a(Rt,e,[t,n]),remove:e=>{let t=a(Ee,e,[]);t&&a(Ct,t,[e])},attribute:(e,t)=>a(Lt,e,[t]),attributes:e=>a(Ot,e,[]),setAttribute:(e,t,n)=>a(jt,e,[t,n]),removeAttribute:(e,t)=>a(It,e,[t]),matches:(e,t)=>a(Dt,e,[t]),all:(e,t)=>z(a(Bt,e,[t])),closeDialog:e=>a(Wt,e,[]),shadow:e=>a(Ht,e,[{mode:"closed"}]),style:(e,t)=>{let n=a(Pe,e,[]);return[a(Jt,n,[t]),a(Yt,n,[t])]},setStyle:(e,t,n,r="")=>a(Qt,a(Pe,e,[]),[t,n,r]),observer:e=>new Te(e),observe:(e,t,n)=>a(Vt,e,[t,n]),record:e=>({type:a(zt,e,[]),target:a(Zt,e,[]),added:z(a(_t,e,[])),removed:z(a(Kt,e,[]))}),adopted:Me&&Fe?{count:e=>a(Me,e,[]).length,clear:e=>a(Fe,e,[[]])}:void 0,frame:e=>a(Gt,globalThis,[e]),popover:$e&&Ne?{show:e=>a($e,e,[]),hide:e=>a(Ne,e,[])}:void 0,target:e=>a(Xt,e,[]),stop:e=>a(en,e,[])}:void 0;var M=class extends Error{constructor(n,r){super(`the app could not run (${n})`);m(this,"reason",n);m(this,"error",r)}};function L(e){try{let t=e instanceof Error?e.name:void 0;return typeof t=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(t)?t:void 0}catch{return}}function U(e){let t=e?.id;if(typeof t!="string"||t==="")return!0;for(let n=0;n<He.length;n++)if(t[n]!==He[n])return!1;return!0}var He="com.wyndlass.";function tn(e=typeof navigator>"u"?"":navigator.userAgent){let t=/Chrom(?:e|ium)\/(\d+)/.exec(e);return t?Number(t[1]):void 0}function Be(e,t=ve.apiVersion,n=tn()){return!(e?.playerApi!==void 0&&e.playerApi>t||e?.webView!==void 0&&n!==void 0&&e.webView>n)}function nn(e){let t;try{t=e.paging?.()}catch{return null}let n=t;return n&&Number.isInteger(n.count)&&n.count>1&&typeof n.ms=="number"&&n.ms>0?{count:n.count,ms:n.ms}:null}var ce=new Map;function rn(){let e=Le(8),t="";for(let n=0;n<8;n++)t+=on[e[n]%26];return t}var on="abcdefghijklmnopqrstuvwxyz",sn=0;function We(e,t,n){let r=ce.get(e);if(!r){let o=ae.make([t],"text/javascript");r=import(o).then(s=>an(s,n),s=>{throw s instanceof M?s:new M("load",L(s))}).finally(()=>ae.revoke(o)),ce.set(e,r),r.catch(()=>ce.delete(e))}return r}function an(e,t){let n=e.default;if(typeof n=="function"&&n.prototype instanceof HTMLElement){let r=n.prototype[ke];if(typeof r!="function")throw new M("shape");let o=class extends n{},s=`${t?.element??"wl-app"}-${rn()}${++sn}`;try{pe.define(s,o)}catch(c){throw new M("shape",L(c))}return{tag:s,shape:"class",attach:(c,k)=>{Ce(r,c,[k])}}}if(U(t)&&typeof e.element=="string"&&pe.get(e.element))return{tag:e.element,shape:"legacy"};throw new M("shape")}var dn=3,pn=6e4,Q=class{constructor(t,n){m(this,"element",t);m(this,"o",n);m(this,"faults",[]);m(this,"halted",!1)}get stopped(){return this.halted}has(t){try{return!this.halted&&typeof this.element[t]=="function"}catch{return!1}}update(t){this.halted||this.run(()=>this.element.update?.(t))}reset(){this.halted||this.run(()=>this.element.reset?.())}paging(){return this.halted?null:nn(this.element)}hook(t,n){if(this.halted)return Promise.resolve();let r;try{r=this.element[t]?.(n)}catch(o){return this.fault(o),Promise.resolve()}return Promise.resolve(r).then(()=>{},o=>this.fault(o))}stop(t){if(!this.halted){this.halted=!0;try{this.element.remove()}catch{}this.o.dispose?.(),this.o.onStop(t)}}dispose(){this.o.dispose?.()}run(t){try{t()}catch(n){this.fault(n)}}fault(t){let n=(this.o.now??Date.now)();this.faults=this.faults.filter(r=>n-r<pn),this.faults.push(n),this.faults.length>=dn&&this.stop(L(t))}},dr=24*3600*1e3;var X="wyndlass.frame";function ze(e,t){return!!e&&(e.apps?e.apps(t):!U(t))}var ln=new Set(["load","shape","unsupported","error","late","overran"]),Ue=50,qe=250,ue=class{constructor(t,n,r,o){m(this,"frame",t);m(this,"port",n);m(this,"o",o);m(this,"halted",!1);m(this,"pages");m(this,"hooks");m(this,"pending",new Map);m(this,"next",0);m(this,"told");m(this,"last");m(this,"quiet");this.hooks=new Set(r.hooks),this.pages=r.paging}get stopped(){return this.halted}has(t){return!this.halted&&this.hooks.has(t)}paging(){return this.halted?null:this.pages}update(t){if(this.halted)return;let n=performance.now(),r=this.last;this.last={t,at:n};let o=this.told;if(o&&(o.running?Math.abs(t-(o.t+(n-o.at)))<=Ue:t===o.t)){o.running&&this.watch();return}let s=!!r&&n-r.at<qe&&t>r.t&&Math.abs(t-r.t-(n-r.at))<=Ue;this.told={t,at:n,running:s},this.post({type:"clock",t,running:s}),s&&this.watch()}reset(){this.halted||this.post({type:"reset"})}hook(t,n){if(this.halted)return Promise.resolve();let r=++this.next;return new Promise(o=>{this.pending.set(r,o),this.post({type:"hook",id:r,kind:t,options:n})})}stop(t){this.halted||(this.halted=!0,this.frame.remove(),this.dispose(),this.o.onStop(t))}dispose(){clearTimeout(this.quiet),this.port.close();for(let t of this.pending.values())t();this.pending.clear()}heard(t){t.type==="paging"?this.pages=_e(t.paging):t.type==="hooked"&&typeof t.id=="number"?(this.pending.get(t.id)?.(),this.pending.delete(t.id)):t.type==="stopped"&&this.stop(Ke(t.error))}post(t){try{this.port.postMessage(t)}catch{}}watch(){clearTimeout(this.quiet),this.quiet=setTimeout(()=>{!this.told?.running||!this.last||this.halted||(this.told={...this.last,running:!1},this.post({type:"clock",t:this.last.t,running:!1}))},qe)}};async function Ze(e,t,n,r,o,s,c,k,h,i){let l=e.appInfo??n.info,u;try{u=new URL(h.frames).origin}catch{return i("unsupported")}let p=document.createElement("iframe");p.setAttribute("sandbox","allow-scripts"),p.setAttribute("referrerpolicy","no-referrer"),p.title=l?.id??"app",p.tabIndex=-1,p.dataset.frame=l?.id??"",p.style.cssText="display:block;width:100%;height:100%;border:0;pointer-events:none;background:transparent;color-scheme:normal";let b=new Promise(S=>{p.addEventListener("load",()=>S(!0),{once:!0}),setTimeout(()=>S(!1),s)});p.src=`${u}/`,t.append(p);let x=(S,w)=>(p.remove(),i(S,w));if(!await b||!p.contentWindow)return x("load");let{port1:A,port2:q}=new MessageChannel,N={type:"init",element:{id:e.id,type:"app",app:e.app,props:e.props??{},...e.binding?{binding:e.binding}:{},...e.appInfo?{appInfo:e.appInfo}:{}},bundle:{hash:n.hash,bytes:n.bytes.slice(),...n.info?{info:n.info}:{}},...r.place?{place:r.place}:{},screen:r.screen??null,timeZone:r.timeZone,locale:r.locale,preview:!!r.preview,clock:{t:o.now(),running:!1,startedAt:o.startedAt()},fonts:mn(e.props??{},h),snapshots:fn(e,r),deadlineMs:s};return new Promise(S=>{let w,P=!1,E=I=>{P||(P=!0,clearTimeout(T),S(I))},T=setTimeout(()=>{w?(E({app:w,late:!0}),w.update(o.now())):(A.close(),E(x("load")))},s+1e3),R=!1;A.onmessage=I=>{let d=I.data;if(!(!d||typeof d!="object")){if(d.type==="ask"){R&&cn(d,r,A);return}if(w){d.type==="ready"?(E({app:w,late:d.late===!0}),w.update(o.now())):w.heard(d);return}d.type==="hello"&&!R?(R=!0,A.postMessage(N,[N.bundle.bytes.buffer])):d.type==="made"&&Array.isArray(d.hooks)?(w=new ue(p,A,{type:"made",hooks:d.hooks.filter(g=>g==="enter"||g==="exit"),paging:_e(d.paging)},{onStop:k}),c(w)):d.type==="failed"&&(A.close(),E(x(ln.has(d.reason)?d.reason:"error",Ke(d.error))))}},p.contentWindow.postMessage({type:X},"*",[q])})}async function cn(e,t,n){if(typeof e.id!="number")return;let r=(o,s=[])=>{try{n.postMessage(o,s)}catch{}};try{if(!t.data)throw new Error("no data service");if(e.what==="data"&&typeof e.source=="string")r({type:"answer",id:e.id,value:await t.data.get(e.source,un(e.params))});else if(e.what==="image"&&typeof e.path=="string"){let o=await t.data.imageBlob(e.path),s=new Uint8Array(await o.arrayBuffer());r({type:"answer",id:e.id,value:{bytes:s,type:o.type}},[s.buffer])}else throw new TypeError("not a question")}catch(o){r({type:"answer",id:e.id,error:L(o)??"Error"})}}function un(e){let t={};if(e&&typeof e=="object")for(let[n,r]of Object.entries(e))(typeof r=="string"||typeof r=="number"||typeof r=="boolean")&&(t[n]=r);return t}function _e(e){let t=e;return t&&Number.isInteger(t.count)&&t.count>1&&typeof t.ms=="number"&&t.ms>0?{count:t.count,ms:t.ms}:null}function Ke(e){return typeof e=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(e)?e:void 0}function mn(e,t){let n=new Set;for(let o of Object.values(e)){let s=o?.family;o&&typeof o=="object"&&typeof s=="string"&&n.add(s)}let r=[];for(let o of t.fonts??[]){let s=n.has(o.family)?t.font?.(o.blob):void 0;s&&r.push({family:o.family,weight:o.weight,style:o.style,blob:o.blob,bytes:s.slice()})}return r}function fn(e,t){let n=new Set;e.binding?.table&&n.add(e.binding.table);for(let o of Object.values(e.props??{}))typeof o=="string"&&/^tbl_[0-9a-z]{26}$/.test(o)&&n.add(o);let r={};for(let o of n){let s=t.snapshot?.(o);s&&(r[o]=s)}return r}function Ve(e){let t=De?.setStyle??((n,r,o,s)=>n.style.setProperty(r,o,s));t(e,"contain","strict","important"),t(e,"overflow","clip","important")}var hn=["name","type","tags","location","site","district","region","organization","path","timeZone"],gn=["text","date","currency","truncate"],Ge={site:"SITE",district:"DISTRICT",region:"REGION",organization:"ORGANIZATION"};function ee(e,t,n,r){if(!e)return n==="timeZone"&&t?t:void 0;if(n==="fields")return r&&e.fields?.[r]||void 0;let o=e.path??[];switch(n){case"name":return e.name||void 0;case"type":return e.type||void 0;case"tags":return e.tags?.length?e.tags.join(", "):void 0;case"location":return o.at(-1)?.name;case"path":return o.length?o.map(s=>s.name).join(" \u203A "):void 0;case"timeZone":return t||void 0;default:return Ge[n]?[...o].reverse().find(s=>s.kind===Ge[n])?.name:void 0}}function yn(e,t){if(e==null||e==="")return;let n=String(e);if(t==="currency"){let r=Number(e);return Number.isFinite(r)?r.toFixed(2):n}if(t==="date"){let r=Date.parse(n);return Number.isNaN(r)?n:new Date(r).toISOString().slice(0,10)}return t==="truncate"&&n.length>40?n.slice(0,39)+"\u2026":n}function Je(e,t){return(e.source==="screen"?ee(t.screen,t.timeZone,e.field,e.key):yn(t.tables?.[e.table]?.rows?.[e.row??0]?.[e.column],e.format))??e.fallback??""}function bn(e){let t=/^\s*(?:screen\.(fields\.[a-z][A-Za-z0-9]{0,31}|[A-Za-z]+)|table\.([A-Za-z0-9_-]+)\[(\d+)\](?:\.([A-Za-z0-9_]+)|\["((?:[^"\\]|\\.)*)"\]))\s*((?:\|\s*(?:[a-z]+|"(?:[^"\\]|\\.)*")\s*)*)$/.exec(e);if(!t)return null;let n=[...t[6].matchAll(/\|\s*([a-z]+|"((?:[^"\\]|\\.)*)")/g)],r=n.find(s=>s[1].startsWith('"'))?.[2]?.replace(/\\(.)/g,"$1"),o=n.find(s=>!s[1].startsWith('"'))?.[1];return t[1]!==void 0?o?null:t[1].startsWith("fields.")?{source:"screen",field:"fields",key:t[1].slice(7),...r!==void 0?{fallback:r}:{}}:hn.includes(t[1])?{source:"screen",field:t[1],...r!==void 0?{fallback:r}:{}}:null:o&&!gn.includes(o)?null:{source:"table",table:t[2],row:Number(t[3]),column:t[4]??t[5].replace(/\\(.)/g,"$1"),...o?{format:o}:{},...r!==void 0?{fallback:r}:{}}}function Ye(e,t){return e.replace(/\{{2}([^{}]*)\}{2}/g,(n,r)=>{let o=bn(r);return o?Je(o,t):n})}function Qe(e,t){return Object.fromEntries(Object.entries(e).map(([n,r])=>[n,typeof r=="string"?Ye(r,t):r]))}var me=e=>e==null||e==="";function et(e,t,n,r,o,s){return wn(e,t,n,r,{now:o,timeZone:s})}function wn(e,t,n,r,o){if(n===null&&(t==="eq"||t==="ne"))return t==="eq"?me(e):!me(e);if(me(e)||n===null||n===void 0)return!1;switch(r){case"number":case"currency":{let s=Number(e),c=Number(n);return typeof n!="boolean"&&n!==""&&Number.isFinite(s)&&Number.isFinite(c)&&fe(t,s-c)}case"boolean":{let s=n===!0||n==="true"?!0:n===!1||n==="false"?!1:void 0;return s!==void 0&&(t==="eq"?e===s:t==="ne"?e!==s:!1)}case"date":{let s=n==="today"?vn(o.now,o.timeZone).date:String(n),c=String(e).slice(0,10);return/^\d{4}-\d{2}-\d{2}$/.test(s)&&fe(t,c<s?-1:c>s?1:0)}case"datetime":{let s=Date.parse(String(e)),c=n==="now"?o.now:Date.parse(String(n));return!Number.isNaN(s)&&!Number.isNaN(c)&&fe(t,s-c)}default:{let s=String(e).toLowerCase(),c=String(n).toLowerCase();return t==="eq"?s===c:t==="ne"?s!==c:t==="contains"?s.includes(c):!1}}}function fe(e,t){switch(e){case"eq":return t===0;case"ne":return t!==0;case"lt":return t<0;case"lte":return t<=0;case"gt":return t>0;case"gte":return t>=0;default:return!1}}var Xe=new Map;function vn(e,t){let n=Xe.get(t);n||(n=new Intl.DateTimeFormat("en-US",{timeZone:t,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),Xe.set(t,n));let r=Object.fromEntries(n.formatToParts(new Date(e)).map(o=>[o.type,o.value]));return{date:`${r.year}-${r.month}-${r.day}`,minute:Number(r.hour)%24*60+Number(r.minute),day:String(r.weekday).slice(0,3).toLowerCase()}}var tt=e=>!!(e?.filter?.length||e?.sort||e?.limit),kn=e=>e==null||e==="";function An(e,t){if(!kn(e))switch(t){case"number":case"currency":{let n=Number(e);return Number.isFinite(n)?n:void 0}case"datetime":{let n=Date.parse(String(e));return Number.isNaN(n)?void 0:n}case"boolean":return e===!0||String(e).toLowerCase()==="true"?1:0;case"date":return String(e).slice(0,10);default:return String(e).toLowerCase()}}function xn(e,t){let n=Array.from(e),r=Array.from(t);for(let o=0;o<n.length&&o<r.length;o++){let s=n[o].codePointAt(0)-r[o].codePointAt(0);if(s!==0)return s}return n.length-r.length}function Sn(e,t,n,r,o){let s=e,c=Array.isArray(s?.rows)?s.rows:[],k=Array.isArray(s?.columns)?s.columns:void 0,h=i=>k?.find(l=>l.key===i)?.type??"text";for(let i of t.filter??[]){if(k&&!k.some(p=>p.key===i.column))return[];let l=i.value;if(l!==null&&typeof l=="object"){let p=l;if(l=ee(n,r,p.screen,p.key),l===void 0)return[]}let u=h(i.column);c=c.filter(p=>et(p[i.column],i.op,l,u,o,r))}if(t.sort){let{column:i}=t.sort,l=h(i),u=t.sort.order==="desc"?-1:1;c=c.map((p,b)=>({row:p,i:b,key:An(p[i],l)})).sort((p,b)=>{if(p.key===void 0||b.key===void 0)return p.key===void 0&&b.key===void 0?p.i-b.i:p.key===void 0?1:-1;let x=typeof p.key=="number"&&typeof b.key=="number"?p.key-b.key:xn(String(p.key),String(b.key));return x===0?p.i-b.i:x<0?-u:u}).map(p=>p.row)}return t.limit?c.slice(0,t.limit):c}function nt(e,t,n,r,o){return!e||typeof e!="object"?e:{...e,rows:Sn(e,t,n,r,o)}}function En(e){return{screen:e?.screen??null,timeZone:e?.timeZone,tables:new Proxy({},{get:(t,n)=>typeof n=="string"?e?.snapshot?.(n):void 0})}}async function ot(e,t,n,r,o,s,c,k=()=>{},h){let i=e.appInfo??n.info;Ve(t),i?.id&&(t.dataset.app=i.id);let l=(d,g)=>(t.dataset.appFailed=d,{failed:d,...g?{error:g}:{}});if(!Be(i?.requires))return l("unsupported");let u=r.appHost;if(u&&!u.custom&&!U(i))return l("unsupported");if(ze(h,i))return Ze(e,t,n,r,o,s,c,k,h,l);let p=e.app;if(u?.held?.(p))return t.dataset.appHeld="",{held:!0};await u?.mounting?.(p,i?.id);let b=!1,x=()=>{b||(b=!0,u?.unmounted?.(p))},A;try{A=await We(n.hash,n.bytes,i)}catch(d){return x(),d instanceof M?l(d.reason,d.error):l("load")}let q=A.tag,N=i?.id?r.controllerFor?.({id:i.id,instance:e.id,capability:i.player??"read"}):void 0,S,w=new Promise(d=>{S=d}),P,E={app:i?.id??e.app.id,element:q,props:Qe(e.props??{},En(r)),place:r.place,screen:r.screen??null,timeZone:r.timeZone??Intl.DateTimeFormat().resolvedOptions().timeZone,locale:r.locale??navigator.language,preview:!!r.preview,now:o.now,startedAt:o.startedAt,ready:()=>S(),data:(d,g)=>r.data?r.data.get(d,g):Promise.reject(new Error("no data service")),kept:(d,g)=>r.data?.kept?.(d,g),image:d=>r.data?r.data.image(d):Promise.reject(new Error("no data service")),snapshot:d=>{let g=r.snapshot?.(d)??null;if(!g||e.binding?.table!==d||!tt(e.binding))return g;let D=nt(g,e.binding,r.screen,E.timeZone,o.now());return P?.from===g&&P.rows.length===D.rows.length&&P.rows.every((at,dt)=>at===D.rows[dt])?P.snapshot:(P={from:g,rows:D.rows,snapshot:D},D)},...e.binding?{binding:e.binding}:{},...e.sensors?.includes("impressions")&&r.impressions?.measuring?{impressions:{now:()=>r.impressions.now(),on:d=>r.impressions.on(d)}}:{},...N?{controller:N}:{}},T;try{T=je(q),A.shape==="class"?A.attach(T,E):T.wyndlass=E}catch(d){return N?.dispose(),x(),l("error",L(d))}T.style.cssText="display:block;width:100%;height:100%",u?.contained?.(t,T);let R=new Q(T,{onStop:k,dispose:()=>{N?.dispose(),x()}});t.append(T),c(R);let I=await Promise.race([w.then(()=>!1),new Promise(d=>setTimeout(()=>d(!0),s))]);return R.update(E.now()),{app:R,late:I}}var rt=new Set;async function st(e,t){for(let n of e){let r=`${n.family}/${n.weight??400}/${n.style??"normal"}/${n.blob}`,o=t(n.blob);if(!(rt.has(r)||!o||typeof FontFace>"u"))try{let s=new FontFace(n.family,o,{weight:String(n.weight??400),style:n.style??"normal"});document.fonts.add(await s.load()),rt.add(r)}catch{}}}var te,F,O={t:0,at:0,running:!1},he={},ge="null",ye=new Map,Tn=0,$=e=>te.postMessage(e),be=()=>O.running?O.t+(performance.now()-O.at):O.t;addEventListener("message",e=>{te||e.source!==parent||e.data?.type!==X||!e.ports[0]||(te=e.ports[0],te.onmessage=t=>Mn(t.data),$({type:"hello"}))});function Mn(e){switch(e.type){case"init":Fn(e);break;case"clock":O={t:e.t,at:performance.now(),running:e.running},F?.update(be()),we();break;case"hook":{let t=e.id;(F?F.hook(e.kind,e.options):Promise.resolve()).then(()=>$({type:"hooked",id:t}));break}case"reset":F?.reset();break;case"snapshots":he=e.snapshots;break;case"answer":{let t=ye.get(e.id);ye.delete(e.id),e.error!==void 0?t?.reject(Object.assign(new Error("the host could not answer"),{name:e.error})):t?.resolve(e.value);break}}}function it(e){let t=++Tn;return new Promise((n,r)=>{ye.set(t,{resolve:n,reject:r}),$({type:"ask",id:t,...e})})}function we(){let e=F?.paging()??null,t=JSON.stringify(e);t!==ge&&(ge=t,$({type:"paging",paging:e}))}async function Fn(e){if(F)return;O={t:e.clock.t,at:performance.now(),running:e.clock.running},he=e.snapshots;let t=new Map,n=(i,l)=>`${i}?${JSON.stringify(Object.keys(l).sort().map(u=>[u,l[u]]))}`,o={data:{get:async(i,l)=>{let u=await it({what:"data",source:i,params:l});return t.set(n(i,l),u),u},kept:(i,l)=>t.get(n(i,l)),image:async i=>{let{bytes:l,type:u}=await it({what:"image",path:i});return URL.createObjectURL(new Blob([l],{type:u}))}},place:e.place,screen:e.screen,timeZone:e.timeZone,locale:e.locale,preview:e.preview,snapshot:i=>he[i]??null},s={now:be,startedAt:()=>e.clock.startedAt},c=new Map(e.fonts.map(i=>[i.blob,i.bytes]));await st(e.fonts,i=>c.get(i));let k=document.getElementById("app"),h=await ot(e.element,k,e.bundle,o,s,e.deadlineMs,i=>{F=i,ge=JSON.stringify(i.paging()),$({type:"made",hooks:["enter","exit"].filter(l=>i.has(l)),paging:i.paging()})},i=>$({type:"stopped",...i?{error:i}:{}}));"failed"in h?$({type:"failed",reason:h.failed,...h.error?{error:h.error}:{}}):"app"in h&&($({type:"ready",late:h.late}),we(),Pn())}function Pn(){let e=0,t=n=>{F?.stopped||(O.running&&F?.update(be()),n-e>1e3&&(e=n,we()),requestAnimationFrame(t))};requestAnimationFrame(t)}})();
2
+ "use strict";(()=>{var vt=Object.defineProperty;var kt=(e,t,n)=>t in e?vt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>kt(e,typeof t!="symbol"?t+"":t,n);var Te={apiVersion:2,properties:[{name:"device",description:"What the device is: id, type, shell and versions, model, resolution and orientation. Fixed while the player runs."},{name:"mode",description:"Where player-core runs: player, preview (in the CMS) or designer. In preview and the designer, commands change nothing."},{name:"timeZone",description:"The player's time zone (IANA), from the host.",event:"time.changed"},{name:"locale",description:"The player's locale (BCP 47), from the host."},{name:"connection",description:"Whether the player is connected to Wyndlass.",event:"connection.changed"},{name:"state",description:"starting, playing, paused, stopped (the standby screen), idle (nothing to play) or error.",event:"state.changed"},{name:"held",description:"A remote pause or stop in effect, by whom and since when; absent when none.",event:"state.changed"},{name:"content",description:"The manifest playing, when it was issued and switched to; absent when there is none.",event:"content.updated"},{name:"now",description:"What each zone shows: the item, its blob and content type, when it started, and its duration and position when known.",event:"item.start"},{name:"audio",description:"The player's volume (0 to 100) and whether it is muted; only when the host can set them.",event:"audio.changed"},{name:"display",description:"The display's power and brightness; only when the host can report them.",event:"display.changed"},{name:"capabilities",description:"What the host can do: audio, display power, display brightness."}],events:[{name:"state.changed",description:"The player's state changed (playing, paused, stopped, idle, error), and who changed it."},{name:"content.updated",description:"A new manifest was switched to."},{name:"item.start",description:"An item started in a zone (source start)."},{name:"item.end",description:"An item ended in a zone (source end): completed, skipped, stopped, error, replaced by new content, or finished by its app."},{name:"item.error",description:"An item could not be shown."},{name:"connection.changed",description:"The player connected to Wyndlass or lost its connection."},{name:"audio.changed",description:"The volume or mute changed, and who changed it."},{name:"display.changed",description:"The display's power or brightness changed, and who changed it."},{name:"time.changed",description:"The clock was adjusted or the time zone changed."},{name:"app.command",description:"A command for apps on the screen (apps.sendCommand); an app reacts if it knows the name."},{name:"app.failed",description:"An app on the screen could not run, or misbehaved (WP-0100): its bundle did not load, it was not an app, it needs a newer player, it kept throwing and was stopped, or it was late or overran a clip."},{name:"app.held",description:"An app version was held back on this screen until the next manifest, after the page stopped answering twice in an hour while it was on screen (WP-0100)."}],commands:[{name:"player.status",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/status",description:"Everything about the player now: device, mode, time zone, locale, connection, state (and any hold), the content playing, what each zone shows, audio, display and what the host can do. Changes nothing.",idempotent:!0},{name:"player.events",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/events",result:"player-api-results.schema.json#/$defs/events",description:"The player's recent events (the last 200), oldest first, after a sequence number: state changes, content updates, item starts and ends, errors, connection, audio, display and time. Pass next as after to poll for more. Changes nothing.",idempotent:!0},{name:"time.now",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/timeNow",description:"The player's current time, its time zone and UTC offset, and whether its clock is synchronized. Changes nothing.",idempotent:!0},{name:"player.play",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Resumes or starts playback on the screen. From a remote caller it also ends a remote pause or stop.",idempotent:!0},{name:"player.pause",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Freezes the screen on what it shows now: the loop's timer and any media stop where they are. From a remote caller it holds until a remote player.play or a restart; apps cannot resume it, and new content arrives paused.",idempotent:!0},{name:"player.stop",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Stops playback and shows the standby screen (black, display on). From a remote caller it holds until a remote player.play or a restart; apps cannot restart it, and new content arrives stopped.",idempotent:!0},{name:"player.next",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Skips to the next item in a zone (main by default); the current item ends as skipped."},{name:"player.previous",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Goes back to the previous item in a zone (main by default); the current item ends as skipped."},{name:"player.playItem",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/playItem",result:"player-api-results.schema.json#/$defs/now",description:"Shows a given item of the content playing now in a zone (main by default), then continues the loop from it. Refused for an item the playing manifest does not have."},{name:"audio.setVolume",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setVolume",result:"player-api-results.schema.json#/$defs/audio",description:"Sets the player's volume, 0 to 100. Only on players whose host can set it to a level.",needs:"audio.volume",idempotent:!0},{name:"audio.setMuted",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setMuted",result:"player-api-results.schema.json#/$defs/audio",description:"Mutes or unmutes the player. Only on players whose host can.",needs:"audio",idempotent:!0},{name:"display.setPower",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setPower",result:"player-api-results.schema.json#/$defs/display",description:"Turns the display on or to standby. Only on players whose host can. Standby blanks the screen until it is turned on.",needs:"display.power",idempotent:!0},{name:"display.setBrightness",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setBrightness",result:"player-api-results.schema.json#/$defs/display",description:"Sets the display's brightness, 0 to 100. Only on players whose host can.",needs:"display.brightness",idempotent:!0},{name:"apps.sendCommand",access:"control",apps:"none",remote:!0,params:"player-api-params.schema.json#/$defs/sendCommand",result:"player-api-results.schema.json#/$defs/delivered",description:"Sends a named command, with an optional argument, to the apps on the screen; an app that knows the name reacts. Returns how many apps listen."},{name:"app.finish",access:"control",apps:"read",remote:!1,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/none",description:"An app ends its own slot now: its item ends as finished and the loop moves on. Apps only; not a tool."}],host:[{name:"host.describe",kind:"request",description:"What the device is, its time zone, locale, connection and capabilities.",result:"player-api-host.schema.json#/$defs/describeResult"},{name:"host.time",kind:"request",description:"The host's time and time zone.",result:"player-api-common.schema.json#/$defs/timeNow"},{name:"host.getAudio",kind:"request",description:"The player's volume and mute.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.setAudio",kind:"request",description:"Sets the volume, the mute or both; answers the audio after.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.getDisplay",kind:"request",description:"The display's power and brightness.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.setDisplay",kind:"request",description:"Sets the display's power, brightness or both; answers the display after.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.adRequest",kind:"request",description:"Asks Wyndlass's ad broker for one slot of a break over the host's NATS connection (WP-0075); the result is the broker's ad-decision (ad-decision.schema.json). Only a host whose describe says ads is asked."},{name:"host.mediaPrepare",kind:"request",description:"Lays a video out behind the page and prepares it to its first frame; host.mediaState says when (ADR 0055)."},{name:"host.mediaShow",kind:"request",description:"Shows and hides prepared videos; replies once its layer has changed."},{name:"host.mediaControl",kind:"request",description:"Plays, pauses or seeks a video, or asks for its position."},{name:"host.mediaRelease",kind:"request",description:"Removes a video and frees its decoder."},{name:"host.reply",kind:"reply",description:"The host's answer to a request: result or error (unsupported, failed)."},{name:"host.connection",kind:"event",description:"The connection to Wyndlass changed."},{name:"host.timeChanged",kind:"event",description:"The clock was adjusted or the time zone changed."},{name:"host.audioChanged",kind:"event",description:"The volume or mute changed outside the player."},{name:"host.displayChanged",kind:"event",description:"The display changed outside the player."},{name:"host.mediaState",kind:"event",description:"A video was prepared, started, ended or failed, or its position was asked for."},{name:"host.presence",kind:"event",description:"What one of the host's sensors sees now: the presences in view, with random ids that never leave the page (WP-0105)."},{name:"host.sensing",kind:"event",description:"The site's sensing mode as the host applies it, and its sensors (WP-0105)."},{name:"host.serialPorts",kind:"event",description:"The host's sensor controller ports, and which are open (WP-0043)."},{name:"host.serialLine",kind:"event",description:"One line from a sensor controller, as it arrived (WP-0043)."},{name:"host.serialWrite",kind:"request",description:"Writes lines to a sensor controller, a gap apart; replies once written (WP-0043)."},{name:"host.bleWatch",kind:"request",description:"What the page listens for: beacons' iBeacon UUIDs, Eddystone namespaces and addresses; replies once the scan follows it. Sent only to a host that has sent host.bleState (WP-0044)."},{name:"host.bleAdverts",kind:"event",description:"Beacon frames that match the watch, at most four a second of each kind from each beacon (WP-0044)."},{name:"host.bleState",kind:"event",description:"The host's Bluetooth scanner: scanning, idle, off, refused, missing or failed; sent only by a host that has one (WP-0044)."},{name:"host.gpioPins",kind:"event",description:"The GPIO pins the host reads, with their values (WP-0044)."},{name:"host.gpioEdge",kind:"event",description:"A GPIO pin changed (WP-0044)."},{name:"host.mcp",kind:"relay",description:"An MCP message from ctl.dev.{deviceId}, for player-core to answer."},{name:"core.ready",kind:"core",description:"player-core is ready to answer host.mcp; until then the shell answers -32002 itself."},{name:"core.mcpReply",kind:"core",description:"player-core's answer to a host.mcp, for the shell to send back."},{name:"core.sensors",kind:"core",description:"The page's sensor controllers, channels and inputs, and the beacons it hears, whenever they change, for the heartbeat and sensors.recent (WP-0043, WP-0044)."}]};var Me=Symbol.for("wyndlass.app.attach");var De=Reflect.apply,de=Object.getOwnPropertyDescriptor,Q=Object.getPrototypeOf,b=globalThis,a=(e,t,n)=>De(e,t,n),k=(e,t)=>e?de(e,t)?.get:void 0,pe=(e,t)=>e?de(e,t)?.set:void 0,g=e=>b[e]?.prototype,He=(e,t,n)=>De(e,t,n);var qn=Date.now,zn=new TextEncoder,_n=new TextDecoder,Zn=TextEncoder.prototype.encode,Kn=TextDecoder.prototype.decode;var Vn=k(Q(Uint8Array.prototype),"length");var xt=Uint8Array,St=e=>new xt(e),Et=[];for(let e=0;e<256;e++)Et[e]=(e<16?"0":"")+e.toString(16);var Tt=Object.create(null);for(let e=0;e<64;e++)Tt["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[e]]=e;var Fe=b.crypto?.subtle,le=Fe?Q(Fe):void 0,ae=b.crypto,Mt=ae?Q(ae).getRandomValues:void 0,Gn=le?.digest,Jn=le?.importKey,Yn=le?.sign;var Be=e=>a(Mt,ae,[St(e)]),Qn={setTimeout:b.setTimeout,clearTimeout:b.clearTimeout,setInterval:b.setInterval,clearInterval:b.clearInterval};var Xn=b.fetch;var q=g("MessagePort"),er=q&&q.postMessage,tr=pe(q,"onmessage"),nr=q&&q.start,ce=g("MessageEvent"),rr=k(ce,"data"),or=k(ce,"ports"),sr=k(ce,"source");var We=g("EventTarget"),ir=We?.addEventListener,ar=We?.removeEventListener;var dr=b.Worker,G=g("Worker"),pr=G&&G.postMessage,lr=pe(G,"onmessage"),cr=pe(G,"onerror");var Pe=b.Blob,J=b.URL,Re=J?.createObjectURL,Ft=J?.revokeObjectURL,ue=Pe&&Re?{make:(e,t)=>a(Re,J,[new Pe(e,{type:t})]),revoke:e=>a(Ft,J,[e])}:void 0,U=b.customElements,me=U?Q(U):void 0,Pt=me?.define,Rt=me?.get,fe=U&&me?{define:(e,t)=>a(Pt,U,[e,t]),get:e=>a(Rt,U,[e])}:void 0,$t=b.document,Y=g("Document"),Nt=Y?.createElement,ur=Y?.createElementNS,Ot=WeakSet.prototype.add,mr=WeakSet.prototype.has,Ct=new WeakSet;var Lt=(e,t)=>(a(Ot,e,[t]),t);function Ue(e){return Lt(Ct,a(Nt,$t,[e]))}var z=g("Node"),he=g("Element"),X=g("HTMLElement"),D=e=>k(z,e),$e=D("parentNode"),jt=D("firstChild"),It=D("lastChild"),Dt=D("nextSibling"),Ht=D("nodeType"),Bt=D("childNodes"),Wt=k(he,"localName"),Ut=z?.appendChild,qt=z?.insertBefore,zt=z?.removeChild,L=he,_t=L?.getAttribute,Zt=L?.getAttributeNames,Kt=L?.setAttribute,Vt=L?.removeAttribute,Gt=L?.matches,Jt=L?.attachShadow,Yt=L?.querySelectorAll,Qt=g("HTMLDialogElement")?.close,Xt=k(g("NodeList"),"length"),en=g("NodeList")?.item,ee=g("MutationRecord"),tn=k(ee,"type"),nn=k(ee,"target"),rn=k(ee,"addedNodes"),on=k(ee,"removedNodes"),Ne=b.MutationObserver,sn=g("MutationObserver")?.observe,qe=Y?de(Y,"adoptedStyleSheets"):void 0,Oe=qe?.get,Ce=qe?.set,an=b.requestAnimationFrame,Le=k(X,"style"),te=g("CSSStyleDeclaration"),dn=te?.getPropertyValue,pn=te?.getPropertyPriority,ln=te?.setProperty,je=X?.showPopover,Ie=X?.hidePopover,cn=k(g("Event"),"target"),un=g("Event")?.stopImmediatePropagation,V=e=>{let t=[];for(let n=0,r=a(Xt,e,[]);n<r;n++)t[n]=a(en,e,[n]);return t},ze=Ne&&z&&he&&X&&te?{parent:e=>a($e,e,[]),first:e=>a(jt,e,[]),last:e=>a(It,e,[]),next:e=>a(Dt,e,[]),isElement:e=>a(Ht,e,[])===1,name:e=>a(Wt,e,[]),children:e=>V(a(Bt,e,[])),append:(e,t)=>a(Ut,e,[t]),insert:(e,t,n)=>a(qt,e,[t,n]),remove:e=>{let t=a($e,e,[]);t&&a(zt,t,[e])},attribute:(e,t)=>a(_t,e,[t]),attributes:e=>a(Zt,e,[]),setAttribute:(e,t,n)=>a(Kt,e,[t,n]),removeAttribute:(e,t)=>a(Vt,e,[t]),matches:(e,t)=>a(Gt,e,[t]),all:(e,t)=>V(a(Yt,e,[t])),closeDialog:e=>a(Qt,e,[]),shadow:e=>a(Jt,e,[{mode:"closed"}]),style:(e,t)=>{let n=a(Le,e,[]);return[a(dn,n,[t]),a(pn,n,[t])]},setStyle:(e,t,n,r="")=>a(ln,a(Le,e,[]),[t,n,r]),observer:e=>new Ne(e),observe:(e,t,n)=>a(sn,e,[t,n]),record:e=>({type:a(tn,e,[]),target:a(nn,e,[]),added:V(a(rn,e,[])),removed:V(a(on,e,[]))}),adopted:Oe&&Ce?{count:e=>a(Oe,e,[]).length,clear:e=>a(Ce,e,[[]])}:void 0,frame:e=>a(an,globalThis,[e]),popover:je&&Ie?{show:e=>a(je,e,[]),hide:e=>a(Ie,e,[])}:void 0,target:e=>a(cn,e,[]),stop:e=>a(un,e,[])}:void 0;var P=class extends Error{constructor(n,r){super(`the app could not run (${n})`);m(this,"reason",n);m(this,"error",r)}};function j(e){try{let t=e instanceof Error?e.name:void 0;return typeof t=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(t)?t:void 0}catch{return}}function _(e){let t=e?.id;if(typeof t!="string"||t==="")return!0;for(let n=0;n<_e.length;n++)if(t[n]!==_e[n])return!1;return!0}var _e="com.wyndlass.";function mn(e=typeof navigator>"u"?"":navigator.userAgent){let t=/Chrom(?:e|ium)\/(\d+)/.exec(e);return t?Number(t[1]):void 0}function Ze(e,t=Te.apiVersion,n=mn()){return!(e?.playerApi!==void 0&&e.playerApi>t||e?.webView!==void 0&&n!==void 0&&e.webView>n)}function fn(e){let t;try{t=e.paging?.()}catch{return null}let n=t;return n&&Number.isInteger(n.count)&&n.count>1&&typeof n.ms=="number"&&n.ms>0?{count:n.count,ms:n.ms}:null}var ge=new Map;function hn(){let e=Be(8),t="";for(let n=0;n<8;n++)t+=gn[e[n]%26];return t}var gn="abcdefghijklmnopqrstuvwxyz",yn=0;function Ke(e,t,n){let r=ge.get(e);if(!r){let o=ue.make([t],"text/javascript");r=import(o).then(s=>bn(s,n),s=>{throw s instanceof P?s:new P("load",j(s))}).finally(()=>ue.revoke(o)),ge.set(e,r),r.catch(()=>ge.delete(e))}return r}function bn(e,t){let n=e.default;if(typeof n=="function"&&n.prototype instanceof HTMLElement){let r=n.prototype[Me];if(typeof r!="function")throw new P("shape");let o=class extends n{},s=`${t?.element??"wl-app"}-${hn()}${++yn}`;try{fe.define(s,o)}catch(c){throw new P("shape",j(c))}return{tag:s,shape:"class",attach:(c,A)=>{He(r,c,[A])}}}if(_(t)&&typeof e.element=="string"&&fe.get(e.element))return{tag:e.element,shape:"legacy"};throw new P("shape")}var wn=3,vn=6e4,ne=class{constructor(t,n){m(this,"element",t);m(this,"o",n);m(this,"faults",[]);m(this,"halted",!1)}get stopped(){return this.halted}has(t){try{return!this.halted&&typeof this.element[t]=="function"}catch{return!1}}update(t){this.halted||this.run(()=>this.element.update?.(t))}reset(){this.halted||this.run(()=>this.element.reset?.())}paging(){return this.halted?null:fn(this.element)}hook(t,n){if(this.halted)return Promise.resolve();let r;try{r=this.element[t]?.(n)}catch(o){return this.fault(o),Promise.resolve()}return Promise.resolve(r).then(()=>{},o=>this.fault(o))}stop(t){if(!this.halted){this.halted=!0;try{this.element.remove()}catch{}this.o.dispose?.(),this.o.onStop(t)}}dispose(){this.o.dispose?.()}run(t){try{t()}catch(n){this.fault(n)}}fault(t){let n=(this.o.now??Date.now)();this.faults=this.faults.filter(r=>n-r<vn),this.faults.push(n),this.faults.length>=wn&&this.stop(j(t))}},br=24*3600*1e3;var re="wyndlass.frame";function Ye(e,t){return!!e&&(e.apps?e.apps(t):!_(t))}var kn=new Set(["load","shape","unsupported","error","late","overran"]),Ve=50,Ge=250,ye=class{constructor(t,n,r,o){m(this,"frame",t);m(this,"port",n);m(this,"o",o);m(this,"halted",!1);m(this,"pages");m(this,"hooks");m(this,"pending",new Map);m(this,"next",0);m(this,"told");m(this,"last");m(this,"quiet");this.hooks=new Set(r.hooks),this.pages=r.paging}get stopped(){return this.halted}has(t){return!this.halted&&this.hooks.has(t)}paging(){return this.halted?null:this.pages}update(t){if(this.halted)return;let n=performance.now(),r=this.last;this.last={t,at:n};let o=this.told;if(o&&(o.running?Math.abs(t-(o.t+(n-o.at)))<=Ve:t===o.t)){o.running&&this.watch();return}let s=!!r&&n-r.at<Ge&&t>r.t&&Math.abs(t-r.t-(n-r.at))<=Ve;this.told={t,at:n,running:s},this.post({type:"clock",t,running:s}),s&&this.watch()}reset(){this.halted||this.post({type:"reset"})}snapshots(t){this.halted||this.post({type:"snapshots",snapshots:t})}hook(t,n){if(this.halted)return Promise.resolve();let r=++this.next;return new Promise(o=>{this.pending.set(r,o),this.post({type:"hook",id:r,kind:t,options:n})})}stop(t){this.halted||(this.halted=!0,this.frame.remove(),this.dispose(),this.o.onStop(t))}dispose(){this.o.dispose?.(),clearTimeout(this.quiet),this.port.close();for(let t of this.pending.values())t();this.pending.clear()}heard(t){t.type==="paging"?this.pages=Xe(t.paging):t.type==="hooked"&&typeof t.id=="number"?(this.pending.get(t.id)?.(),this.pending.delete(t.id)):t.type==="stopped"&&this.stop(et(t.error))}post(t){try{this.port.postMessage(t)}catch{}}watch(){clearTimeout(this.quiet),this.quiet=setTimeout(()=>{!this.told?.running||!this.last||this.halted||(this.told={...this.last,running:!1},this.post({type:"clock",t:this.last.t,running:!1}))},Ge)}};async function Qe(e,t,n,r,o,s,c,A,y,i){let l=e.appInfo??n.info,u;try{u=new URL(y.frames).origin}catch{return i("unsupported")}let d=document.createElement("iframe");d.setAttribute("sandbox","allow-scripts"),d.setAttribute("referrerpolicy","no-referrer"),d.title=l?.id??"app",d.tabIndex=-1,d.dataset.frame=l?.id??"",d.style.cssText="display:block;width:100%;height:100%;border:0;pointer-events:none;background:transparent;color-scheme:normal";let w=new Promise(T=>{d.addEventListener("load",()=>T(!0),{once:!0}),setTimeout(()=>T(!1),s)});d.src=`${u}/`,t.append(d);let E=(T,v)=>(d.remove(),i(T,v));if(!await w||!d.contentWindow)return E("load");let{port1:x,port2:Z}=new MessageChannel,O={type:"init",element:{id:e.id,type:"app",app:e.app,props:e.props??{},...e.binding?{binding:e.binding}:{},...e.appInfo?{appInfo:e.appInfo}:{}},bundle:{hash:n.hash,bytes:n.bytes.slice(),...n.info?{info:n.info}:{}},...r.place?{place:r.place}:{},screen:r.screen??null,timeZone:r.timeZone,locale:r.locale,preview:!!r.preview,clock:{t:o.now(),running:!1,startedAt:o.startedAt()},fonts:Sn(e.props??{},y),snapshots:Je(e,r),deadlineMs:s};return new Promise(T=>{let v,$=!1,M=B=>{$||($=!0,clearTimeout(C),T(B))},C=setTimeout(()=>{v?(M({app:v,late:!0}),v.update(o.now())):(x.close(),M(E("load")))},s+1e3),S=!1;x.onmessage=B=>{let h=B.data;if(!(!h||typeof h!="object")){if(h.type==="ask"){S&&An(h,r,x);return}if(v){h.type==="ready"?(M({app:v,late:h.late===!0}),v.update(o.now())):v.heard(h);return}if(h.type==="hello"&&!S)S=!0,x.postMessage(O,[O.bundle.bytes.buffer]);else if(h.type==="made"&&Array.isArray(h.hooks)){let W=tt(e),K,p=new ye(d,x,{type:"made",hooks:h.hooks.filter(f=>f==="enter"||f==="exit"),paging:Xe(h.paging)},{onStop:A,dispose:()=>K?.()});K=r.onSnapshots?.(f=>{f.some(F=>W.has(F))&&p.snapshots(Je(e,r))}),v=p,c(v)}else h.type==="failed"&&(x.close(),M(E(kn.has(h.reason)?h.reason:"error",et(h.error))))}},d.contentWindow.postMessage({type:re},"*",[Z])})}async function An(e,t,n){if(typeof e.id!="number")return;let r=(o,s=[])=>{try{n.postMessage(o,s)}catch{}};try{if(!t.data)throw new Error("no data service");if(e.what==="data"&&typeof e.source=="string")r({type:"answer",id:e.id,value:await t.data.get(e.source,xn(e.params))});else if(e.what==="image"&&typeof e.path=="string"){let o=await t.data.imageBlob(e.path),s=new Uint8Array(await o.arrayBuffer());r({type:"answer",id:e.id,value:{bytes:s,type:o.type}},[s.buffer])}else throw new TypeError("not a question")}catch(o){r({type:"answer",id:e.id,error:j(o)??"Error"})}}function xn(e){let t={};if(e&&typeof e=="object")for(let[n,r]of Object.entries(e))(typeof r=="string"||typeof r=="number"||typeof r=="boolean")&&(t[n]=r);return t}function Xe(e){let t=e;return t&&Number.isInteger(t.count)&&t.count>1&&typeof t.ms=="number"&&t.ms>0?{count:t.count,ms:t.ms}:null}function et(e){return typeof e=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(e)?e:void 0}function Sn(e,t){let n=new Set;for(let o of Object.values(e)){let s=o?.family;o&&typeof o=="object"&&typeof s=="string"&&n.add(s)}let r=[];for(let o of t.fonts??[]){let s=n.has(o.family)?t.font?.(o.blob):void 0;s&&r.push({family:o.family,weight:o.weight,style:o.style,blob:o.blob,bytes:s.slice()})}return r}function tt(e){let t=new Set;e.binding?.table&&t.add(e.binding.table);for(let n of Object.values(e.props??{}))typeof n=="string"&&/^tbl_[0-9a-z]{26}$/.test(n)&&t.add(n);return t}function Je(e,t){let n={};for(let r of tt(e)){let o=t.snapshot?.(r);o&&(n[r]=o)}return n}function nt(e){let t=ze?.setStyle??((n,r,o,s)=>n.style.setProperty(r,o,s));t(e,"contain","strict","important"),t(e,"overflow","clip","important")}var En=["name","type","tags","location","site","district","region","organization","path","timeZone"],Tn=["text","date","currency","truncate"],rt={site:"SITE",district:"DISTRICT",region:"REGION",organization:"ORGANIZATION"};function oe(e,t,n,r){if(!e)return n==="timeZone"&&t?t:void 0;if(n==="fields")return r&&e.fields?.[r]||void 0;let o=e.path??[];switch(n){case"name":return e.name||void 0;case"type":return e.type||void 0;case"tags":return e.tags?.length?e.tags.join(", "):void 0;case"location":return o.at(-1)?.name;case"path":return o.length?o.map(s=>s.name).join(" \u203A "):void 0;case"timeZone":return t||void 0;default:return rt[n]?[...o].reverse().find(s=>s.kind===rt[n])?.name:void 0}}function Mn(e,t){if(e==null||e==="")return;let n=String(e);if(t==="currency"){let r=Number(e);return Number.isFinite(r)?r.toFixed(2):n}if(t==="date"){let r=Date.parse(n);return Number.isNaN(r)?n:new Date(r).toISOString().slice(0,10)}return t==="truncate"&&n.length>40?n.slice(0,39)+"\u2026":n}function ot(e,t){return(e.source==="screen"?oe(t.screen,t.timeZone,e.field,e.key):Mn(t.tables?.[e.table]?.rows?.[e.row??0]?.[e.column],e.format))??e.fallback??""}function st(e){let t=/^\s*(?:screen\.(fields\.[a-z][A-Za-z0-9]{0,31}|[A-Za-z]+)|table\.([A-Za-z0-9_-]+)\[(\d+)\](?:\.([A-Za-z0-9_]+)|\["((?:[^"\\]|\\.)*)"\]))\s*((?:\|\s*(?:[a-z]+|"(?:[^"\\]|\\.)*")\s*)*)$/.exec(e);if(!t)return null;let n=[...t[6].matchAll(/\|\s*([a-z]+|"((?:[^"\\]|\\.)*)")/g)],r=n.find(s=>s[1].startsWith('"'))?.[2]?.replace(/\\(.)/g,"$1"),o=n.find(s=>!s[1].startsWith('"'))?.[1];return t[1]!==void 0?o?null:t[1].startsWith("fields.")?{source:"screen",field:"fields",key:t[1].slice(7),...r!==void 0?{fallback:r}:{}}:En.includes(t[1])?{source:"screen",field:t[1],...r!==void 0?{fallback:r}:{}}:null:o&&!Tn.includes(o)?null:{source:"table",table:t[2],row:Number(t[3]),column:t[4]??t[5].replace(/\\(.)/g,"$1"),...o?{format:o}:{},...r!==void 0?{fallback:r}:{}}}function it(e,t){return e.replace(/\{{2}([^{}]*)\}{2}/g,(n,r)=>{let o=st(r);return o?ot(o,t):n})}function at(e){return[...e.matchAll(/\{{2}([^{}]*)\}{2}/g)].some(t=>st(t[1])!==null)}function be(e,t){return Object.fromEntries(Object.entries(e).map(([n,r])=>[n,typeof r=="string"?it(r,t):r]))}var we=e=>e==null||e==="";function pt(e,t,n,r,o,s){return Fn(e,t,n,r,{now:o,timeZone:s})}function Fn(e,t,n,r,o){if(n===null&&(t==="eq"||t==="ne"))return t==="eq"?we(e):!we(e);if(we(e)||n===null||n===void 0)return!1;switch(r){case"number":case"currency":{let s=Number(e),c=Number(n);return typeof n!="boolean"&&n!==""&&Number.isFinite(s)&&Number.isFinite(c)&&ve(t,s-c)}case"boolean":{let s=n===!0||n==="true"?!0:n===!1||n==="false"?!1:void 0;return s!==void 0&&(t==="eq"?e===s:t==="ne"?e!==s:!1)}case"date":{let s=n==="today"?Pn(o.now,o.timeZone).date:String(n),c=String(e).slice(0,10);return/^\d{4}-\d{2}-\d{2}$/.test(s)&&ve(t,c<s?-1:c>s?1:0)}case"datetime":{let s=Date.parse(String(e)),c=n==="now"?o.now:Date.parse(String(n));return!Number.isNaN(s)&&!Number.isNaN(c)&&ve(t,s-c)}default:{let s=String(e).toLowerCase(),c=String(n).toLowerCase();return t==="eq"?s===c:t==="ne"?s!==c:t==="contains"?s.includes(c):!1}}}function ve(e,t){switch(e){case"eq":return t===0;case"ne":return t!==0;case"lt":return t<0;case"lte":return t<=0;case"gt":return t>0;case"gte":return t>=0;default:return!1}}var dt=new Map;function Pn(e,t){let n=dt.get(t);n||(n=new Intl.DateTimeFormat("en-US",{timeZone:t,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),dt.set(t,n));let r=Object.fromEntries(n.formatToParts(new Date(e)).map(o=>[o.type,o.value]));return{date:`${r.year}-${r.month}-${r.day}`,minute:Number(r.hour)%24*60+Number(r.minute),day:String(r.weekday).slice(0,3).toLowerCase()}}var lt=e=>!!(e?.filter?.length||e?.sort||e?.limit),Rn=e=>e==null||e==="";function $n(e,t){if(!Rn(e))switch(t){case"number":case"currency":{let n=Number(e);return Number.isFinite(n)?n:void 0}case"datetime":{let n=Date.parse(String(e));return Number.isNaN(n)?void 0:n}case"boolean":return e===!0||String(e).toLowerCase()==="true"?1:0;case"date":return String(e).slice(0,10);default:return String(e).toLowerCase()}}function Nn(e,t){let n=Array.from(e),r=Array.from(t);for(let o=0;o<n.length&&o<r.length;o++){let s=n[o].codePointAt(0)-r[o].codePointAt(0);if(s!==0)return s}return n.length-r.length}function On(e,t,n,r,o){let s=e,c=Array.isArray(s?.rows)?s.rows:[],A=Array.isArray(s?.columns)?s.columns:void 0,y=i=>A?.find(l=>l.key===i)?.type??"text";for(let i of t.filter??[]){if(A&&!A.some(d=>d.key===i.column))return[];let l=i.value;if(l!==null&&typeof l=="object"){let d=l;if(l=oe(n,r,d.screen,d.key),l===void 0)return[]}let u=y(i.column);c=c.filter(d=>pt(d[i.column],i.op,l,u,o,r))}if(t.sort){let{column:i}=t.sort,l=y(i),u=t.sort.order==="desc"?-1:1;c=c.map((d,w)=>({row:d,i:w,key:$n(d[i],l)})).sort((d,w)=>{if(d.key===void 0||w.key===void 0)return d.key===void 0&&w.key===void 0?d.i-w.i:d.key===void 0?1:-1;let E=typeof d.key=="number"&&typeof w.key=="number"?d.key-w.key:Nn(String(d.key),String(w.key));return E===0?d.i-w.i:E<0?-u:u}).map(d=>d.row)}return t.limit?c.slice(0,t.limit):c}function ct(e,t,n,r,o){return!e||typeof e!="object"?e:{...e,rows:On(e,t,n,r,o)}}function ut(e){return{screen:e?.screen??null,timeZone:e?.timeZone,tables:new Proxy({},{get:(t,n)=>typeof n=="string"?e?.snapshot?.(n):void 0})}}async function ft(e,t,n,r,o,s,c,A=()=>{},y){let i=e.appInfo??n.info;nt(t),i?.id&&(t.dataset.app=i.id);let l=(p,f)=>(t.dataset.appFailed=p,{failed:p,...f?{error:f}:{}});if(!Ze(i?.requires))return l("unsupported");let u=r.appHost;if(u&&!u.custom&&!_(i))return l("unsupported");if(Ye(y,i))return Qe(e,t,n,r,o,s,c,A,y,l);let d=e.app;if(u?.held?.(d))return t.dataset.appHeld="",{held:!0};await u?.mounting?.(d,i?.id);let w=!1,E=()=>{w||(w=!0,u?.unmounted?.(d))},x;try{x=await Ke(n.hash,n.bytes,i)}catch(p){return E(),p instanceof P?l(p.reason,p.error):l("load")}let Z=x.tag,O=i?.id?r.controllerFor?.({id:i.id,instance:e.id,capability:i.player??"read"}):void 0,T,v=new Promise(p=>{T=p}),$,M=new Set,C={app:i?.id??e.app.id,element:Z,props:be(e.props??{},ut(r)),place:r.place,screen:r.screen??null,timeZone:r.timeZone??Intl.DateTimeFormat().resolvedOptions().timeZone,locale:r.locale??navigator.language,preview:!!r.preview,now:o.now,startedAt:o.startedAt,ready:()=>T(),data:(p,f)=>r.data?r.data.get(p,f):Promise.reject(new Error("no data service")),kept:(p,f)=>r.data?.kept?.(p,f),image:p=>r.data?r.data.image(p):Promise.reject(new Error("no data service")),snapshot:p=>{let f=r.snapshot?.(p)??null;if(!f||e.binding?.table!==p||!lt(e.binding))return f;let F=ct(f,e.binding,r.screen,C.timeZone,o.now());return $?.from===f&&$.rows.length===F.rows.length&&$.rows.every((bt,wt)=>bt===F.rows[wt])?$.snapshot:($={from:f,rows:F.rows,snapshot:F},F)},...e.binding?{binding:e.binding}:{},...r.onSnapshots?{onSnapshot:p=>(M.add(p),()=>{M.delete(p)})}:{},...e.sensors?.includes("impressions")&&r.impressions?.measuring?{impressions:{now:()=>r.impressions.now(),on:p=>r.impressions.on(p)}}:{},...O?{controller:O}:{}},S;try{S=Ue(Z),x.shape==="class"?x.attach(S,C):S.wyndlass=C}catch(p){return O?.dispose(),E(),l("error",j(p))}S.style.cssText="display:block;width:100%;height:100%",u?.contained?.(t,S);let B=Object.values(e.props??{}).some(p=>typeof p=="string"&&at(p)),h=r.onSnapshots?.(p=>{B&&(C.props=be(e.props??{},ut(r)));for(let f of p)for(let F of[...M])try{F(f)}catch{}}),W=new ne(S,{onStop:A,dispose:()=>{h?.(),M.clear(),O?.dispose(),E()}});t.append(S),c(W);let K=await Promise.race([v.then(()=>!1),new Promise(p=>setTimeout(()=>p(!0),s))]);return W.update(C.now()),{app:W,late:K}}var mt=new Set;async function ht(e,t){for(let n of e){let r=`${n.family}/${n.weight??400}/${n.style??"normal"}/${n.blob}`,o=t(n.blob);if(!(mt.has(r)||!o||typeof FontFace>"u"))try{let s=new FontFace(n.family,o,{weight:String(n.weight??400),style:n.style??"normal"});document.fonts.add(await s.load()),mt.add(r)}catch{}}}var Cn=Object.freeze,Ln=Object.values,gt=Reflect.apply;function se(e){if(e===null||typeof e!="object")return e;for(let t of gt(Ln,Object,[e]))se(t);return gt(Cn,Object,[e])}var ie,R,I={t:0,at:0,running:!1},H={},ke=new Set,Ae="null",xe=new Map,jn=0,N=e=>ie.postMessage(e),Se=()=>I.running?I.t+(performance.now()-I.at):I.t;addEventListener("message",e=>{ie||e.source!==parent||e.data?.type!==re||!e.ports[0]||(ie=e.ports[0],ie.onmessage=t=>In(t.data),N({type:"hello"}))});function In(e){switch(e.type){case"init":Dn(e);break;case"clock":I={t:e.t,at:performance.now(),running:e.running},R?.update(Se()),Ee();break;case"hook":{let t=e.id;(R?R.hook(e.kind,e.options):Promise.resolve()).then(()=>N({type:"hooked",id:t}));break}case"reset":R?.reset();break;case"snapshots":{let t=H;H=se(e.snapshots);let n=[...new Set([...Object.keys(t),...Object.keys(H)])].filter(r=>JSON.stringify(t[r])!==JSON.stringify(H[r]));if(n.length)for(let r of[...ke])r(n);break}case"answer":{let t=xe.get(e.id);xe.delete(e.id),e.error!==void 0?t?.reject(Object.assign(new Error("the host could not answer"),{name:e.error})):t?.resolve(e.value);break}}}function yt(e){let t=++jn;return new Promise((n,r)=>{xe.set(t,{resolve:n,reject:r}),N({type:"ask",id:t,...e})})}function Ee(){let e=R?.paging()??null,t=JSON.stringify(e);t!==Ae&&(Ae=t,N({type:"paging",paging:e}))}async function Dn(e){if(R)return;I={t:e.clock.t,at:performance.now(),running:e.clock.running},H=se(e.snapshots);let t=new Map,n=(i,l)=>`${i}?${JSON.stringify(Object.keys(l).sort().map(u=>[u,l[u]]))}`,o={data:{get:async(i,l)=>{let u=await yt({what:"data",source:i,params:l});return t.set(n(i,l),u),u},kept:(i,l)=>t.get(n(i,l)),image:async i=>{let{bytes:l,type:u}=await yt({what:"image",path:i});return URL.createObjectURL(new Blob([l],{type:u}))}},place:e.place,screen:e.screen,timeZone:e.timeZone,locale:e.locale,preview:e.preview,snapshot:i=>H[i]??null,onSnapshots:i=>(ke.add(i),()=>{ke.delete(i)})},s={now:Se,startedAt:()=>e.clock.startedAt},c=new Map(e.fonts.map(i=>[i.blob,i.bytes]));await ht(e.fonts,i=>c.get(i));let A=document.getElementById("app"),y=await ft(e.element,A,e.bundle,o,s,e.deadlineMs,i=>{R=i,Ae=JSON.stringify(i.paging()),N({type:"made",hooks:["enter","exit"].filter(l=>i.has(l)),paging:i.paging()})},i=>N({type:"stopped",...i?{error:i}:{}}));"failed"in y?N({type:"failed",reason:y.failed,...y.error?{error:y.error}:{}}):"app"in y&&(N({type:"ready",late:y.late}),Ee(),Hn())}function Hn(){let e=0,t=n=>{R?.stopped||(I.running&&R?.update(Se()),n-e>1e3&&(e=n,Ee()),requestAnimationFrame(t))};requestAnimationFrame(t)}})();
@@ -6,6 +6,7 @@
6
6
  <!-- An app's frame in the CMS (WP-0100 decision 11). Its policy is the frames host's header (the CMS image), not a meta.
7
7
  A classic script: this document's origin is opaque, so a module would be fetched with CORS. -->
8
8
  <style>:root{color-scheme:normal}html,body,#app{margin:0;width:100%;height:100%;overflow:hidden;background:transparent}</style>
9
+ <script src="./made.js"></script>
9
10
  <script src="./frame.js" defer></script>
10
11
  </head>
11
12
  <body><div id="app"></div></body>
@@ -0,0 +1,2 @@
1
+ /* @wyndlass/app-sdk's preview: what an app makes, counted in its frame. MIT, (c) Northwynd Labs LLC. */
2
+ "use strict";(()=>{function g(n){let m=0,s=c=>{for(let o=c.firstChild;o;o=o.nextSibling){if(o.nodeType===1){m++;let u=o.shadowRoot;u&&s(u)}s(o)}};return s(n),m}function b(n,m){let s={},c=t=>{s[t]=(s[t]??0)+1},o=(t,e)=>new Proxy(t,{construct:(a,l,i)=>(c(e),Reflect.construct(a,l,i)),apply:(a,l,i)=>(c(e),Reflect.apply(a,l,i))}),u=n.Intl;for(let t of["DateTimeFormat","NumberFormat","Collator","PluralRules","RelativeTimeFormat","ListFormat","DisplayNames","Segmenter"]){let e=u[t];e&&(u[t]=o(e,`Intl.${t}`))}let r=(t,e,a)=>{let l=t&&t[e];typeof l=="function"&&(t[e]=function(...i){let p=Reflect.apply(l,this,i),k=a(i,this,p);return k&&c(k),p})},d=t=>t[1]!==void 0?"toLocale":null;for(let t of["toLocaleString","toLocaleDateString","toLocaleTimeString"])r(n.Date?.prototype,t,d);r(n.Number?.prototype,"toLocaleString",d),r(n.Document?.prototype,"createElement",t=>String(t[0]).toLowerCase()==="canvas"?"canvas":null),n.OffscreenCanvas&&(n.OffscreenCanvas=o(n.OffscreenCanvas,"canvas"));let f=new WeakSet,w=(t,e,a)=>!a||typeof a!="object"||f.has(a)?null:(f.add(a),"context");return r(n.HTMLCanvasElement?.prototype,"getContext",w),r(n.OffscreenCanvas?.prototype,"getContext",w),r(n,"createImageBitmap",()=>"bitmap"),r(n.URL,"createObjectURL",()=>"objectUrl"),n.Worker&&(n.Worker=o(n.Worker,"worker")),r(n.EventTarget?.prototype,"addEventListener",(t,e)=>e===void 0||e===n||e===n.document?"listener":null),()=>({kinds:{...s},nodes:m()})}var y=b(self,()=>g(document.getElementById("app")??document));setInterval(()=>parent.postMessage({wyndlassPreview:"made",made:y()},"*"),250);})();
@@ -8,11 +8,11 @@
8
8
  :root { color-scheme: dark; --line: #2c3540; --muted: #93a1b0; --accent: #4f8cff; --danger: #ff6b6b; --ok: #4fd18b; --warn: #f5b84a; }
9
9
  * { box-sizing: border-box; }
10
10
  html, body { margin: 0; height: 100%; background: #12171d; color: #e6edf3; font: 14px/1.4 system-ui, sans-serif; }
11
- body { display: grid; grid-template-columns: 330px 1fr; grid-template-rows: auto 1fr auto; height: 100vh; }
11
+ body { display: grid; grid-template-columns: 330px 1fr; grid-template-rows: auto 1fr auto auto; height: 100vh; }
12
12
  header { grid-column: 1 / 3; display: flex; gap: 12px; align-items: baseline; padding: 10px 16px; border-bottom: 1px solid var(--line); }
13
13
  header h1 { font-size: 16px; margin: 0; }
14
14
  header span { color: var(--muted); }
15
- #controls { grid-row: 2 / 4; overflow: auto; border-right: 1px solid var(--line); padding: 8px 14px 24px; }
15
+ #controls { grid-row: 2 / 5; overflow: auto; border-right: 1px solid var(--line); padding: 8px 14px 24px; }
16
16
  #controls h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 18px 0 8px; }
17
17
  .field { display: flex; flex-direction: column; gap: 3px; margin-bottom: 8px; }
18
18
  .field.inline { flex-direction: row; align-items: center; gap: 8px; }
@@ -24,8 +24,8 @@
24
24
  button { cursor: pointer; }
25
25
  button[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
26
26
  #stage { position: relative; overflow: hidden; background: repeating-conic-gradient(#1a2028 0 25%, #151a21 0 50%) 0 0 / 24px 24px; }
27
- #report { border-top: 1px solid var(--line); padding: 8px 16px; display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: baseline; min-height: 40px; }
28
- #report .ok { color: var(--ok); } #report .late, #report .warn { color: var(--warn); } #report .failed, #controls .failed { color: var(--danger); }
27
+ #report, #made { border-top: 1px solid var(--line); padding: 8px 16px; display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: baseline; min-height: 40px; }
28
+ #report .ok { color: var(--ok); } #report .late, #report .warn, #made .warn { color: var(--warn); } #report .failed, #controls .failed { color: var(--danger); }
29
29
  #report pre { margin: 0; white-space: pre-wrap; color: var(--danger); }
30
30
  #clock-time { font-variant-numeric: tabular-nums; }
31
31
  </style>
@@ -35,6 +35,7 @@
35
35
  <aside id="controls" aria-label="Controls"></aside>
36
36
  <section id="stage" aria-label="The app, as a screen shows it"></section>
37
37
  <section id="report" aria-label="What a screen would report" aria-live="polite"></section>
38
+ <section id="made" aria-label="What it makes while it plays" aria-live="polite"></section>
38
39
  <script src="/page.js"></script>
39
40
  </body>
40
41
  </html>