Module contract: modules can present multiple tiles to the picker
A module may now present a list of tiles instead of a single picker
entry. A server module exports tiles({config, log}) — sync or async,
called on every picker load so the list can be live data (discovered
ProPresenter timers, admin-configured pages); a client-only module can
declare a static tiles array in module.json. The + picker groups the
entries under the module's name; adding one presets that entry's
instance settings, gives the tile the entry's name as its title, and
honors per-entry default/min sizes, all persisted with the layout.
moduleApi.variant tells the instance which entry it was added as. A
throwing or hung tiles() (2 s deadline) falls back to the classic single
entry so a dead upstream can never break the picker; modules without
tiles are byte-for-byte unaffected. Documented in docs/MODULE-GUIDE.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
This commit is contained in:
@@ -59,6 +59,7 @@ shell code changes, ever.
|
||||
| `minSize`, `defaultSize` | no | Tile size in grid cells (12 columns wide; rows are ~72 px). Defaults: min 1×1, default 4×3. |
|
||||
| `configSchema` | no | **Admin config** — server-wide settings (URLs, ports, keys) edited once at `/admin` for everyone. |
|
||||
| `instanceSchema` | no | **Per-tile settings** — chosen in each tile's gear menu and stored with that browser's layout (filters, text sizes, display options). |
|
||||
| `tiles` | no | Fixed multi-tile list for client-only modules; server modules export a dynamic `tiles()` instead. See **Presenting multiple tiles**. |
|
||||
|
||||
Both schemas map field names to specs:
|
||||
|
||||
@@ -107,6 +108,7 @@ export default function create({ root, moduleApi }) {
|
||||
| --- | --- |
|
||||
| `id` | Your module id. |
|
||||
| `instanceId` | Unique per tile — two tiles of the same module get different ids. Handy for keying anything per-tile. |
|
||||
| `variant` | For multi-tile modules: the id of the tile-list entry this tile was added as, `''` otherwise (see **Presenting multiple tiles**). |
|
||||
| `config` | Admin (server-wide) config, read-only, always current. Password fields are absent — they never reach the client. |
|
||||
| `instanceSettings` | This tile's settings: your `instanceSchema` defaults overlaid with whatever this tile has saved. Read it fresh whenever you render — don't cache it. |
|
||||
| `saveInstanceSettings(patch)` | Merge `patch` into this tile's settings and persist them with the layout. No restart happens — you made the change, you already know. Use it for state the user sets *inside* your tile (a toggled filter, a chosen tab). |
|
||||
@@ -260,6 +262,50 @@ module.exports = {
|
||||
shows in `/admin`, and API calls answer 502 until a config fix re-inits
|
||||
it. Validate config early so bad input fails loudly there.
|
||||
|
||||
### Presenting multiple tiles
|
||||
|
||||
By default a module is one entry in the + Add-tile picker. A module can
|
||||
instead present a **list of tiles** — one picker entry each, grouped under
|
||||
the module's name. This is how a module whose content is naturally plural
|
||||
works: one tile per ProPresenter timer, one tile per admin-configured page.
|
||||
|
||||
Export `tiles` from your server entry (sync or async):
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
init({ config, log }) { … },
|
||||
routes({ config, log }) { … },
|
||||
|
||||
tiles({ config, log }) {
|
||||
// e.g. derived from admin config, or discovered from your upstream
|
||||
return config.pages.map((page) => ({
|
||||
id: page.name, // stable id, stored with the tile
|
||||
name: page.name, // picker label AND the tile's title
|
||||
description: page.url, // optional line under the label
|
||||
settings: { page: page.name }, // preset instance settings
|
||||
defaultSize: { w: 4, h: 3 }, // optional per-entry size overrides
|
||||
minSize: { w: 2, h: 2 },
|
||||
}));
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
- `tiles()` runs on **every picker load**, so the list may be live data —
|
||||
keep it fast (answer from state you already hold; don't fetch upstream on
|
||||
demand). A throw or a hang (>2 s) falls back to the classic single entry,
|
||||
so a dead upstream can't break the picker.
|
||||
- When the user adds an entry, its `settings` overlay your `instanceSchema`
|
||||
defaults in that tile's instance settings, the entry's `name` becomes the
|
||||
tile's title, and `moduleApi.variant` carries its `id`.
|
||||
- The tile keeps its variant id and title even if the entry later vanishes
|
||||
from your list (a timer deleted in ProPresenter, a page removed in admin).
|
||||
**Your client decides what that means** — show a clear "no longer
|
||||
configured" state rather than erroring.
|
||||
- A client-only module can declare a fixed list as a `tiles` array in
|
||||
`module.json` instead (same entry shape).
|
||||
- An empty list (or no `tiles` at all) keeps today's behavior: one picker
|
||||
entry, no preset.
|
||||
|
||||
### Server-Sent Events from a module
|
||||
|
||||
For live data, expose a `/stream` route and let clients use
|
||||
|
||||
Reference in New Issue
Block a user