@wyndlass/app-sdk 1.1.1 → 1.1.3
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 +2 -2
- package/dist/index.d.ts +4 -1
- package/dist/react.d.ts +5 -1
- package/dist/react.js +5 -1
- package/package.json +1 -1
- package/skill/wyndlass-app/SKILL.md +4 -0
- package/skill/wyndlass-app/references/contract.md +2 -2
- package/skill/wyndlass-app/references/mcp.md +2 -1
- package/skill/wyndlass-app/references/templates.md +1 -1
- package/templates/react/AGENTS.md +1 -0
- package/templates/react/src/app.tsx +1 -0
- package/templates/vanilla/AGENTS.md +1 -0
package/README.md
CHANGED
|
@@ -135,7 +135,7 @@ export default class QueueBoard extends WyndlassApp {
|
|
|
135
135
|
}
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
- **`WyndlassApp`:** extend it, and write `mount()` (build the shadow DOM, once) and `draw(epochMs)` (draw that moment of the layout's clock). It gives:
|
|
138
|
+
- **`WyndlassApp`:** extend it, and write `mount()` (build the shadow DOM, once) and `draw(epochMs)` (draw that moment of the layout's clock, at every frame: make formatters and other objects in `mount()`, never in `draw`). It gives:
|
|
139
139
|
- `prop(name, default)`, `font()` (the font param as CSS), `now()`, `startedAt()`, `timeZone` and `locale`;
|
|
140
140
|
- `css(text)`: styles for the shadow root, as a constructable stylesheet;
|
|
141
141
|
- `ready()`: says the app may be shown, once, within 5 s of its start;
|
|
@@ -177,7 +177,7 @@ export default reactApp(Board, { css });
|
|
|
177
177
|
|
|
178
178
|
- **`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
179
|
- **`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.
|
|
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
181
|
- **`usePaging(count, ms)`:** the page showing now. It also tells the host the pages.
|
|
182
182
|
- **`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
183
|
- **`useSnapshot(table)`:** a data table's snapshot.
|
package/dist/index.d.ts
CHANGED
|
@@ -72,7 +72,10 @@ export declare abstract class WyndlassApp extends HTMLElement {
|
|
|
72
72
|
protected ready(): void;
|
|
73
73
|
/** Builds the app's shadow DOM, once. */
|
|
74
74
|
protected abstract mount(): void;
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Draws the moment `epochMs` on the layout's clock, at every frame: make formatters and other objects in `mount()`,
|
|
77
|
+
* never here. One made per frame holds memory a screen does not free in time.
|
|
78
|
+
*/
|
|
76
79
|
protected abstract draw(epochMs: number): void;
|
|
77
80
|
update(epochMs: number): void;
|
|
78
81
|
enter(o: HookOptions): void;
|
package/dist/react.d.ts
CHANGED
|
@@ -32,7 +32,11 @@ export declare function useApp(): {
|
|
|
32
32
|
screen: ScreenFacts | null;
|
|
33
33
|
preview: boolean;
|
|
34
34
|
};
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* The layout's clock, drawn again at each of its frames: seeking and pausing show the right moment. The component renders
|
|
37
|
+
* at each one, so make formatters and other objects with `useMemo`, never in the render: one made per frame holds memory a
|
|
38
|
+
* screen does not free in time.
|
|
39
|
+
*/
|
|
36
40
|
export declare function useNow(): number;
|
|
37
41
|
/**
|
|
38
42
|
* Which of `count` pages shows now, each `ms` long from the layout's start, and the pages said to the host, so a layout
|
package/dist/react.js
CHANGED
|
@@ -104,7 +104,11 @@ export function useApp() {
|
|
|
104
104
|
};
|
|
105
105
|
}, [app, bridge]);
|
|
106
106
|
}
|
|
107
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* The layout's clock, drawn again at each of its frames: seeking and pausing show the right moment. The component renders
|
|
109
|
+
* at each one, so make formatters and other objects with `useMemo`, never in the render: one made per frame holds memory a
|
|
110
|
+
* screen does not free in time.
|
|
111
|
+
*/
|
|
108
112
|
export function useNow() {
|
|
109
113
|
const [, bridge] = useBridge();
|
|
110
114
|
return useSyncExternalStore(bridge.subscribe, bridge.time, bridge.time);
|
package/package.json
CHANGED
|
@@ -75,6 +75,8 @@ Ask what should play where, and when. Then `campaign.save` at the place and time
|
|
|
75
75
|
|
|
76
76
|
Raise `version` in `app.json`, then pack and import again (steps 4 and 5). Layouts that name no version take the new one once it is approved and checked. A layout whose app element pins a `version` (the item's) moves only when you save it with the new one: show the person first.
|
|
77
77
|
|
|
78
|
+
**Where it is placed comes from `app.list`, never a guess.** Each of the organization's apps says which layouts place it and how many screens show it now. Tell the person: those layouts and screens take the new version by themselves. Asked to place or schedule it again, say where it is already, and make another layout or campaign only if they want one.
|
|
79
|
+
|
|
78
80
|
## Rules every app keeps
|
|
79
81
|
|
|
80
82
|
Screens or the CMS enforce each of these:
|
|
@@ -86,6 +88,8 @@ Screens or the CMS enforce each of these:
|
|
|
86
88
|
- **Data only from the sources `app.json` declares,** and only those apps may read: `weather`, `news`, `image`, `calendar`.
|
|
87
89
|
- **Network hosts declared truthfully** in `capabilities.network`: a person approves them.
|
|
88
90
|
|
|
91
|
+
One more, which nothing enforces: **make nothing in a draw.** `draw(epochMs)` and a React component run again at every frame, many times a second. Make formatters (`Intl.DateTimeFormat`, `Intl.NumberFormat`) and other objects once: in `mount()`, or with `useMemo` on what they depend on. 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.
|
|
92
|
+
|
|
89
93
|
## When something goes wrong
|
|
90
94
|
|
|
91
95
|
- **A tool you need is missing:** call `whoami`. The import tools are listed only while the organization has imported apps turned on at its top place. An Owner or Admin turns them on in the CMS, under **Settings**, **Imported apps**.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
`class X extends WyndlassApp` (vanilla). Write two methods:
|
|
12
12
|
- `mount()`: build the shadow DOM in `this.root`, once.
|
|
13
|
-
- `draw(epochMs)`: show that moment of the layout's clock. The host calls it on every frame it needs, seeking and pausing included.
|
|
13
|
+
- `draw(epochMs)`: show that moment of the layout's clock. The host calls it on every frame it needs, seeking and pausing included. So it runs many times a second: make formatters and other objects in `mount()`, never here.
|
|
14
14
|
|
|
15
15
|
It gives the element:
|
|
16
16
|
- `prop(name, default)`: a param's value, or the default when it is absent or empty;
|
|
@@ -40,7 +40,7 @@ A paged app also defines `paging()`, returning `{count, ms}`, so the layout can
|
|
|
40
40
|
|
|
41
41
|
`export default reactApp(Component, { css })`: the element's class. The component renders in the app's shadow root, with `css` as its styles. Hooks:
|
|
42
42
|
- `useApp()`: `prop(name, default)`, `ready()`, `timeZone`, `locale`, `place`, `screen`, `preview`;
|
|
43
|
-
- `useNow()`: the layout's clock, drawn again at each of its frames;
|
|
43
|
+
- `useNow()`: the layout's clock, drawn again at each of its frames, so the component renders many times a second: make formatters and other objects with `useMemo`, never in the render;
|
|
44
44
|
- `usePaging(count, ms)`: the page showing now. It also tells the host the pages;
|
|
45
45
|
- `useData(source, params)`: `{answer, error}`. The kept answer at once, then the service's, asked again every 10 minutes;
|
|
46
46
|
- `useSnapshot(table)`: a data table's rows, as `snapshot` gives them, read again each frame.
|
|
@@ -42,7 +42,7 @@ It answers `<zip>: uploaded (<n> bytes). Import it with app.import.` Never read
|
|
|
42
42
|
|
|
43
43
|
## 4. `app.list`
|
|
44
44
|
|
|
45
|
-
Lists Wyndlass's apps, then the organization's own: each with its item (`med_…`), and its newest versions, each:
|
|
45
|
+
Lists Wyndlass's apps, then the organization's own: each with its item (`med_…`), the layouts that place it ("Placed in 1 layout: Lobby (med_…)", or "Placed in no layout you may read"), how many screens show it now, and its newest versions, each:
|
|
46
46
|
- **"being checked, then approved with its import"** or **"…, then held for review":** Wyndlass is scanning and checking it. Call `app.list` again in a little while;
|
|
47
47
|
- **"refused by its checks: <reason>":** fix the cause ([contract.md](contract.md), Limits), raise `version`, pack and import again;
|
|
48
48
|
- **"held for review: a person approves it in the CMS":** give the person the link `app.import` gave, and say why it is held. Wait for them; never approve it yourself;
|
|
@@ -80,6 +80,7 @@ Ask what should play where, and when. Never at the organization's top place unle
|
|
|
80
80
|
2. Import it: steps 1 to 4.
|
|
81
81
|
3. **Layouts that name no version** (`app: {"id": "med_…"}`) take the new one once it is approved and checked.
|
|
82
82
|
4. **A layout that pins one** (`app: {"id": "med_…", "version": <n>}`, the item's version) keeps it. To move it, `layout.save` with the new item version, after showing the person the change.
|
|
83
|
+
5. **Say where it plays from `app.list`:** its layouts and its screens now. They need no new layout or campaign. Make one only when the person asks for another, after telling them where the app is already.
|
|
83
84
|
|
|
84
85
|
## When a screen doesn't show it
|
|
85
86
|
|
|
@@ -28,7 +28,7 @@ Two pages, eight seconds each: a heading and the time, then the weather for the
|
|
|
28
28
|
|
|
29
29
|
1. **Change `app.json` first:** the `params` people set in the designer (each with a `label` and a `default`), the `data` sources it reads, and `capabilities` if it asks for more.
|
|
30
30
|
2. **Read each param** with `prop(name, default)` (vanilla) or `useApp().prop(name, default)` (react), with the same default as `app.json`.
|
|
31
|
-
3. **Draw from the layout's clock:** vanilla's `draw(epochMs)` gets the moment; react's `useNow()` gives it. Work out what changes with time (a page, a countdown, a clock) from that number, never from a timer of your own.
|
|
31
|
+
3. **Draw from the layout's clock:** vanilla's `draw(epochMs)` gets the moment; react's `useNow()` gives it. Work out what changes with time (a page, a countdown, a clock) from that number, never from a timer of your own. Both run at every frame: make a formatter once, as the templates do (`#format` in `mount()`, `useMemo` in react), never in the draw.
|
|
32
32
|
4. **Say `ready()`** as soon as the app has something to show, within 5 s: the kept answer, a default, or the failure.
|
|
33
33
|
5. **Styles** go in `src/app.css`. Size text to the box with `fit()` or the `--w` and `--h` properties ([contract.md](contract.md)), since screens run from 1080p to 4K.
|
|
34
34
|
6. **Images:** import them from `src/` (`import logo from "./logo.png"`: PNG, JPEG, GIF, WebP or SVG), and `pack` inlines each as a data URL, for an `<img>`'s `src` or a style set from code. A `url()` inside `src/app.css` is not bundled.
|
|
@@ -4,6 +4,7 @@ An app for Wyndlass's screens, on `@wyndlass/app-sdk` and React. Its README says
|
|
|
4
4
|
|
|
5
5
|
- **The module is `src/app.tsx`;** its default export is `reactApp(Component, { css })`, the element's class. The component renders in the app's shadow root.
|
|
6
6
|
- **Time:** `useNow()` gives the layout's clock, drawn again at each of its frames; never `Date.now()`, so a seek or a pause shows the right moment.
|
|
7
|
+
- **Nothing made in a render:** the component renders at every frame, many times a second. Make formatters (`Intl.DateTimeFormat`, `Intl.NumberFormat`) and other objects with `useMemo` on what they depend on, 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.
|
|
7
8
|
- **Ready:** call `useApp().ready()` once drawn, within 5 s, or the screen reports the app late. `useData()` gives the answer the screen kept at once, then the service's.
|
|
8
9
|
- **Data:** only through `useData(source, params)`, and only the sources `app.json`'s `data` lists. Pages: `usePaging(count, ms)`.
|
|
9
10
|
- **Styles:** `src/app.css`, passed as `css`, never a `<style>`.
|
|
@@ -15,6 +15,7 @@ function Board() {
|
|
|
15
15
|
// The weather for the screen's place, or Winnipeg's where there is none (the designer's sample).
|
|
16
16
|
const place = app.place ?? { lat: 49.9, lon: -97.14 };
|
|
17
17
|
const { answer, error } = useData<weather.Weather>("weather", { lat: place.lat, lon: place.lon, units: app.prop("units", "metric") });
|
|
18
|
+
// Made once: the component renders at every frame, and a formatter made in each render holds memory (AGENTS.md).
|
|
18
19
|
const time = useMemo(() => new Intl.DateTimeFormat(app.locale, { timeZone: app.timeZone, hour: "numeric", minute: "2-digit",
|
|
19
20
|
...(app.prop("seconds", false) ? { second: "2-digit" } : {}) }), [app]);
|
|
20
21
|
// Ready once there is something to show: the kept answer at once, else the service's answer or its failure.
|
|
@@ -4,6 +4,7 @@ An app for Wyndlass's screens, on `@wyndlass/app-sdk`. Its README says what it i
|
|
|
4
4
|
|
|
5
5
|
- **The module is `src/app.ts`;** its default export is the element's class, extending `WyndlassApp`. `mount()` builds the shadow DOM once; `draw(epochMs)` draws that moment of the layout's clock.
|
|
6
6
|
- **Time:** from `now()` or `draw`'s `epochMs`, never `Date.now()`, so a seek or a pause shows the right moment.
|
|
7
|
+
- **Nothing made in `draw`:** it runs at every frame, many times a second. Make formatters (`Intl.DateTimeFormat`, `Intl.NumberFormat`) and other objects in `mount()`, never in `draw`. 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.
|
|
7
8
|
- **Ready:** call `ready()` once drawn, within 5 s, or the screen reports the app late. Draw the answer `kept()` gives at once, then ask `data()`.
|
|
8
9
|
- **Data:** only through `data(this, source, params)`, and only the sources `app.json`'s `data` lists. Images a data answer names through `image(this, path)`.
|
|
9
10
|
- **Styles:** `this.css(text)`, never a `<style>`. A CSS file the source imports is its text.
|