Widget, Konfiguration und Vorschau
defineWidget mit configSchema und noResolver, Formate (useWidgetFormat), Vorschau-Varianten mit Größe für Katalog und Miniaturen sowie Vorschau-Zustände — mit dem Uhr-Beispiel.
17 Minuten LesezeitApps-SDKStand: 25.10.2026
Auf dieser Seite
Bevor Sie loslegen
- Für wen
- Entwickler, die ein Widget definieren oder die Vorschau-Varianten einer App pflegen.
- Voraussetzung
- das SDK
@szenera/apps-sdkaus dem Quellbaum der Plattform (nicht öffentlich veröffentlicht); Zod für das Konfigurationsschema.
defineWidget baut die Widget-Definition, die im Manifest unter widgets steht: Konfigurationsschema, Größen und die Vorschau-Daten, aus denen Katalog, Miniaturen und die Vorlagen des Editors entstehen. Die Tabellen nennen jedes Feld; das Uhr-Beispiel zeigt sie im Zusammenhang.
Die Felder der Widget-Definition
| Feld | Pflicht | Format | Bedeutung |
|---|---|---|---|
id |
ja | Kleinbuchstaben und Ziffern mit Bindestrichen, 2–100 Zeichen | Der Widget-Typ; unter diesem Schlüssel registrieren runtime- und authoring-Bündel ihre Komponenten. |
appId |
ja | App-Kennung | Muss der id des Manifests gleichen (Querregel beim Laden). |
name |
ja | 1–60 Zeichen | Name in der Editor-Palette. |
description |
ja | max. 300 Zeichen | Kurztext in Palette und Store. |
configSchema |
ja | Zod-Schema; jedes Feld mit .default() und .describe() |
Der Vertrag der Widget-Konfiguration. defineWidget leitet daraus das JSON-Schema ab, das der Katalog ausliefert — siehe Feldtexte und Beispiele. |
noResolver |
Vorgabe false |
boolean | true: rein statisch, rendert allein aus der Konfiguration. false: verlangt einen @WidgetResolver im Server-Teil — fehlt er, lädt die App nicht. |
size.min |
nein | { w, h } in ganzen Bezugspixeln einer 1920 × 1080-Szene |
Der kleinste Rahmen, in dem das Widget noch etwas Sinnvolles zeigt; der Editor lässt die Griffe nicht darunter ziehen. Jede Vorlage muss mindestens so groß sein. |
preview.thumbnailUrl |
ja | Pfad | Miniatur in Editor-Palette und Kachel. |
preview.defaultConfig |
nein | Konfiguration | Startbelegung neu platzierter Instanzen; zugleich die Grundlage jeder Vorschau-Variante. |
preview.mockData |
nein | Nutzlast oder Konfiguration | Beispieldaten für die Vorschau; bei statischen Widgets eine repräsentative Konfiguration. |
preview.variants |
Vorgabe [] |
siehe unten | Die Darstellungen, die das Widget beherrscht, mit size zugleich die Vorlagen im Editor. |
preview.states |
nein | siehe unten, höchstens 12 | Benannte Beispiel-Nutzlasten für die Vorschau im Einstellungs-Fenster. |
preload |
nein | boolean | true: Die Anzeige lädt das Widget verdeckt vor, bevor seine Szene an die Reihe kommt. useWidgetActivity() meldet dann die Phase preload, das Widget lädt, spielt aber nichts ab. Für Widgets mit fremden Inhalten (Seite, Video, Stream), sonst stünde nach der Überblendung eine leere Fläche. |
Jedes Feld der Konfiguration trägt eine Vorgabe, damit eine leere Konfiguration vollständig parst und der Editor ohne Vorbefüllung rendert. Ein Widget-Resolver im Server-Teil parst ctx.config mit demselben Schema erneut; das Schema liegt damit auf beiden Seiten.
Feldtexte und Beispiele
defineWidget übersetzt das Zod-Schema beim Laden des Manifests einmal in ein JSON-Schema-Dokument nach Draft 2020-12 — das ist es, was GET /v1/widget-catalog ausliefert und was ein fremder Client gegen seine Konfiguration prüft. Zwei Dinge steuern Sie dabei bewusst:
Die Feldtexte. JSON-Schema kennt je Feld zwei Texte — title als kurze Überschrift, description als Satz dazu —, Zod kennt nur einen. Beide stehen deshalb in EINEM .describe(), getrennt durch einen senkrechten Strich:
timezone: z
.string()
.max(64)
.default('')
.describe('Zeitzone|IANA-Zeitzone wie „Europe/Berlin". Leer heißt: die Zeitzone der Organisation.'),
Ohne Strich wird der ganze Text zur description, und das Feld bekommt keine Überschrift. Ein Feld ohne .describe() steht ohne Erklärung im Katalog — für jeden, der ohne den Editor arbeitet, ist es dann so gut wie unbeschriftet.
Die Beispiele. examples füllt das SDK selbst: die preview.defaultConfig und je Vorschau-Variante dieselbe Konfiguration mit der Teil-Konfiguration der Variante darüber. Jedes Beispiel wird gegen das Zod-Schema geprüft — bricht eines die eigene Regel, schlägt der Bau der App fehl und nennt das Feld. Ein Widget ohne preview.defaultConfig fällt auf preview.mockData zurück; hat es beides nicht, lässt es sich nicht bauen.
Was das Dokument am Ende sagt, steht unter Widgets konfigurieren.
Vorschau-Varianten
preview.variants ist die einzige Quelle dafür, was eine App zeigen kann: Die Store-Galerie rendert daraus ihre Ansichten, der Bildgenerator leitet daraus ab, welche Miniaturen er aufnimmt, und der Editor bietet die Varianten mit Größe als Vorlagen an. Eine Layout-Option, die hier nicht steht, gibt es im Store nicht.
| Feld | Pflicht | Format | Bedeutung |
|---|---|---|---|
id |
ja | ^[a-z][a-z0-9-]{1,31}$, eindeutig je Widget |
Zugleich das Namenssuffix des Bildes: /apps/<appId>/thumbnails/<widgetId>--<variantId>.png. |
label |
ja | 1–40 Zeichen | Beschriftung der Abbildung („Karten“, „Nächste groß“). |
description |
nein | max. 160 Zeichen | Ein Satz, wofür die Darstellung taugt. |
config |
nein | Teil-Konfiguration | Der Unterschied zu preview.defaultConfig; ein Widget ohne Wahlmöglichkeit deklariert genau eine Variante ohne config. |
size |
nein | { w, h } in ganzen Bezugspixeln einer 1920 × 1080-Szene |
Die Startgröße der Vorlage: Fügt der Editor die Variante ein, bekommt das Widget diesen Rahmen. Ohne size ist die Variante nur Abbildung. |
aspect |
Vorgabe 16:9, bei size abgeleitet |
16:9 · 9:16 |
Bildschirm-Lage der Aufnahme; quer ist der Regelfall. Mit size folgt sie der Größe (quer, solange der Rahmen mindestens so breit wie hoch ist), defineWidget setzt sie, und eine widersprechende Angabe bricht den Bau. |
primary |
Vorgabe false |
boolean | Genau eine Variante je Widget, das Varianten trägt; sie wird die Miniatur in Palette und Kachel und die Vorlage, die ein Klick einfügt. Der Vertrag weist null oder zwei kanonische Varianten ab. |
Größen an Varianten
Jede Variante bekommt eine size, die zu ihrem Zweck passt: eine Uhr als Kachel (600 × 300), ein Nachrichtenband als Leiste (1920 × 120), ein Aushang hochkant (600 × 800), eine Kartenansicht breit (1280 × 720). Die Größe ist der Rahmen, mit dem ein Klick die Vorlage in eine 1920 × 1080-Szene setzt. Auf einer anderen Bühne rechnet der Editor sie über die kurze Seite um. Eine Variante für stehende Displays bekommt eine hohe Größe statt aspect: '9:16', die Lage folgt ihr. Höher als 1080 Bezugspixel ist keine Vorlage, ein Hochkant-Rahmen ist also eine Spalte in der Szene und kein ganzer Bildschirm.
Die Miniatur einer Variante mit Größe zeigt das Widget 1 : 1 in diesem Rahmen, mittig auf einer ruhigen hellen Fläche, mit Name und Maß darunter. Das Bild bleibt 1920 × 1080, bei hoher Größe 1080 × 1920. Eine Variante ohne Größe füllt die Bühne ihrer Lage.
Die Passt-Prüfung rendert jede Variante jeder App in ihrem Rahmen und schlägt fehl, wenn dort etwas übersteht, ein Text angeschnitten oder zu klein ist oder das Widget über den Passt-Bericht einen eingeschalteten Inhalt ausblendet. Sie läuft als Gate im Pull Request und lokal so:
pnpm --filter @szenera-app/<id> build
pnpm app:fit-check --only=<id> # jede Variante, jede Randgröße, jeder Zustand
pnpm app:fit-check --try=<id>:<variante>@600x300 # eine Größe messen, bevor sie im Manifest steht
Gemessen wird die Tinte eines Textes, nicht sein Zeilenkasten. Die Regeln im Einzelnen:
| Regel | Befund |
|---|---|
| Überstand | Ein Element, das selbst zeichnet (Bild, Fläche, Linie), geht über den Rahmen. |
| Angeschnitten oder verdeckt | Ein Text ragt aus dem Rahmen oder aus einem Kasten, der seinen Überlauf kappt, oder verschwindet ganz dahinter (die letzten Zeilen einer Liste mit fester Höhe). |
| Durchgeschnitten | Ein Kasten, der ausdrücklich kürzt (text-overflow: ellipsis, line-clamp), darf eine ganze letzte Zeile mit „…“ enden lassen und die Zeilen danach weglassen. Schneidet er eine Zeile oben oder unten durch die Buchstaben, passt seine Höhe nicht zu seinen Zeilen. |
| Ganz bleiben | Ein Text in einem Element mit data-fit-whole (Uhrzeit, Datum, Preis, Pflichtangaben, Telefon, Quellenangabe) wird nie gekürzt, weder vom Kasten noch mit einer Auslassung im Text. |
| Mindestschrift | Inhalt unter 12, Quellen- und Standzeilen (data-fit-meta) unter 10 Bezugspixeln einer Bühne mit kurzer Seite 1080, in jeder Form. Dieselben Grenzen (MIN_CONTENT_FONT_REF, MIN_META_FONT_REF) halten die Anzeige-Bausteine ein. |
| Text über Text | Zwei Texte überdecken einander. |
| Editor-Text am Display | Nur im Anzeige-Modus (Vorschau-Zustände): ein Satz, der sich an den Bearbeiter wendet oder Technik nennt („in den Einstellungen“, „eintragen“, „Widget“, „Fehler“), oder in einem ruhigen Zustand mehr als ein kurzer neutraler Satz. |
| Passt-Bericht | Das Widget blendet in seiner Vorlage einen eingeschalteten Inhalt aus. |
Ein Widget mit size.min steht im neuen Standard und bekommt dazu den Randgrößen-Satz: die Mindestgröße selbst, je Form den kleinsten Rahmen, den size.min erlaubt, und den größten in der Bezugsszene, Leiste und Spalte im äußersten Seitenverhältnis (volle Breite in Mindesthöhe, volle Höhe in Mindestbreite). Gerendert wird mit der Vorgabe-Config, die alles einschaltet. Dort darf das Widget ausblenden, nur abschneiden und zu kleine Schrift darf es nicht. Die Fehler aller Berichte steckten an diesen Rändern, nicht in den Vorlagen.
Läuft ein Inhalt absichtlich durch seinen Kasten und bewegt ihn ein Skript statt einer CSS-Animation, trägt das bewegte Element oder sein Kasten data-fit-scroll (das Laufband setzt es selbst). Eine Schicht, die nur zum Messen gezeichnet wird, trägt data-fit-measure und zählt nicht. Die Bilder eines Rahmens mit Befund liegen danach unter scratchpad/fit-check/. Schneidet eine App, die noch nicht im neuen Standard steht, in jeder vernünftigen Größe ab, gehört der Rahmen mit einem Satz Grund („eigene Welle“) in die Ausnahmeliste tools/app-screenshots/fit-exceptions.mjs. Dort bleibt er im Bericht sichtbar, und passt er eines Tages, wird die Prüfung rot, bis der Eintrag verschwindet. Eine App im neuen Standard trägt keine Ausnahme.
Vorschau-Zustände
preview.states zeigt im Einstellungs-Fenster, wie das Widget in einer bestimmten Lage aussieht, ohne dass sie gerade eintritt: Regen, Nacht, keine Daten, veraltet. Der Editor bietet die Zustände als Umschalter neben „Echte Daten“ an.
| Feld | Pflicht | Format | Bedeutung |
|---|---|---|---|
id |
ja | ^[a-z][a-z0-9-]{1,31}$, eindeutig je Widget |
Kennung des Zustands. |
label |
ja | 1–40 Zeichen | Beschriftung am Umschalter („Regen“, „Nacht“). |
description |
nein | max. 160 Zeichen | Ein Satz, was der Zustand zeigt. |
mockData |
ja | Nutzlast oder Konfiguration | Die Beispieldaten des Zustands, in derselben Form wie preview.mockData. |
Ein Schema der Nutzlast gibt es nicht. defineWidget prüft deshalb die Form: reine JSON-Daten (keine Funktion, kein Datum, keine Zahl ohne Wert) und dieselbe Art wie preview.mockData (Objekt, Liste oder Einzelwert). Übersetzt wird die Beschriftung unter translations.<sprache>.widgets.<widget>.states.<zustand>.label, wie die einer Variante.
Der Zustand empty ist Pflicht für jedes Widget mit Quelle (noResolver: false), pnpm app:check verlangt ihn in der Zeile „Zustände“. Er zeigt das Widget ohne Daten, bei einem Daten-Widget mit mockData: null (keine Nutzlast, wie vor dem ersten Abruf oder nach einem gescheiterten). Apps, die ihren leeren Zustand erst in ihrer eigenen Welle neu zeichnen, stehen bis dahin in tools/app-kit/src/check/state-exceptions.ts.
Nie Editor-Text am Display. Leer, gestört oder unkonfiguriert heißt am Display eine ruhige Fläche und höchstens ein neutraler Satz, die Erklärung mit dem nächsten Schritt steht im Editor (useWidgetMode(), der Baustein QuietState trennt beides). Die Passt-Prüfung rendert deshalb jeden Zustand im Anzeige-Modus, so wie ein Display ihn zeigt: im Rahmen der primary-Vorlage, die ruhigen Zustände empty und error in jeder Vorlage. Ein Satz an den Bearbeiter oder mehr als zwölf Wörter in einem ruhigen Zustand sind dort ein Befund.
Vorlagen im Editor
Eine Variante mit size ist im Szenen-Editor eine Vorlage. Ihr Inhalt ist preview.defaultConfig mit der Teil-Konfiguration der Variante flach darüber ({ ...defaultConfig, ...variant.config }), genau wie im Bildgenerator. Was der Store zeigt, fügt der Editor deshalb so ein.
| Weg | Was passiert |
|---|---|
| Klick auf die Kachel im App-Bereich | Die kanonische Vorlage (primary) kommt in ihrer Startgröße mittig auf die Bühne. Ist ein Slot der Layout-Vorlage frei, füllt das Widget ihn mit der Vorlage, deren Form am besten zum Slot passt: in einer Spalte die Hochkant-Vorlage, in einer Leiste die Leiste. |
| Pfeil an der Kachel | „ |
| Verweilen auf der Kachel | Die Vorschau zeigt die kanonische Vorlage in ihrem Rahmen, nicht bühnenfüllend. |
| Ziehen auf die Bühne | Startgröße der kanonischen Vorlage um die Drop-Stelle, der freie Slot unter dem Zeiger ist hervorgehoben. |
| Leiste am gewählten Widget | „Vorlage“ trennt, was zum Rahmen passt (gleiche Form, nur der Inhalt wechselt), von dem, was einen neuen Rahmen setzt (mittig um den alten Mittelpunkt). Im Slot wechselt nur der Inhalt. |
| Einstellungs-Fenster | Kacheln je Vorlage, die aktive markiert, „angepasst“, sobald der Inhalt abweicht. Zurücksetzen führt auf die aktive Vorlage. Den Rahmen setzt eine gewählte Vorlage erst beim Übernehmen, zusammen mit der Config in einem Schritt. |
Drei Regeln, die eine App beim Entwerfen ihrer Vorlagen kennen sollte:
- Die Startgröße steht im Bezug 1920 × 1080. Auf einer anderen Bühne rechnet der Editor sie über die kurze Seite um (eine 4K-Szene bekommt das Doppelte) und begrenzt sie, ohne das Seitenverhältnis zu verlieren, auf die Bühne.
- Der Inhalt einer Vorlage sind die Schlüssel ihrer Teil-Konfiguration. Wechselt der Kunde die Vorlage, gehen alle Schlüssel, die irgendeine Vorlage setzt, auf die Vorgabe zurück, darüber kommt die neue Teil-Konfiguration. Was keine Vorlage setzt (ein gewählter Ort, die Fläche), bleibt. Vorlagen, die sich nur in der Größe unterscheiden, dürfen denselben Inhalt tragen.
- Welche Vorlage ein Widget trägt, erkennt der Editor aus Rahmen und Inhalt. Die Instanz merkt sich ihre Vorlage nicht. Zuerst zählt die Form des Rahmens, darunter die Vorlage, deren Inhalt die Config unverändert trägt, sonst die mit dem nächsten Seitenverhältnis.
size.min begrenzt die Griffe auf der Bühne: Kleiner als die Mindestgröße (über die kurze Seite auf die Bühne gerechnet) lässt sich das Widget nicht ziehen. Beim Größenändern zeigt der Editor Form und Maße des Rahmens („Hochkant · 440 × 780“), die Umschalttaste sperrt das Seitenverhältnis.
Ein Widget je App, Vorlagen statt zweiter Widgets
Keine App trägt zwei Widgets. Die App ist die Einheit, die eine Organisation installiert und wieder entfernt — ein Bündel unverwandter Widgets ließe sich nicht bewerten. Was mehrere Darstellungen braucht, löst das über die Vorschau-Varianten und ein Konfigurationsfeld:
- Eine Variante mit
sizeist eine Vorlage. Sie legt die Startgröße des Rahmens fest und mit ihrer Teil-Konfiguration den Inhalt, den der Kunde beim Einfügen bekommt. - Wie der Inhalt innerhalb des Rahmens aussieht, entscheiden Rahmen und Konfiguration — die Form des Rahmens liest das Widget über
useWidgetFormat(), die Wahl des Kunden steht in einem Konfigurationsfeld wiecompositionbei der Uhr.
Ein Widget mit noResolver: false wird auch ohne Player aufgelöst, etwa für die Vorschau im Editor; der Resolver bekommt dann playerId: null und muss diesen Fall verkraften.
Formate: wie ein Widget seinen Rahmen nutzt
Ein Widget steht in einer Szene selten bildschirmfüllend, oft klein, hochkant oder als Leiste. Es ordnet sich deshalb nach dem Rahmen, den die Szene ihm gibt, und zwar nach einer Rechnung für alle Apps. Die Hooks stehen in @szenera/widget-foundation/hooks:
| Hook | Liefert | Wofür |
|---|---|---|
useWidgetFormat(options?) |
{ shape, size, aspect, shortSideRef, width, height } |
Form und Größenklasse des Rahmens; die App zeichnet je Form eine Anordnung. |
useWidgetGeometry() |
{ x, y, width, height, stage: { width, height } } |
Die aufgelöste Box in Bühnenpixeln und die Bühne, ohne DOM-Messung. |
useFitReport() |
{ reportHidden(parts) } |
Meldet dem Editor, welche eingeschalteten Teile im Rahmen gerade fehlen. |
Die Form folgt dem Seitenverhältnis Breite : Höhe:
| Form | Seitenverhältnis | Typischer Platz |
|---|---|---|
band |
ab 3,2 : 1 | Leiste am Rand der Szene |
wide |
1,3 bis 3,2 | liegender Kasten |
square |
0,78 bis 1,3 | Kachel |
tall |
unter 0,78 | Spalte |
Beim Ziehen der Griffe im Editor wechselt die Form erst, wenn das Verhältnis 4 % hinter der Schwelle liegt. So springt ein Widget nicht bei jeder Zeigerbewegung zwischen zwei Anordnungen.
Die Größe misst die kurze Seite des Rahmens in Bezugspixeln einer Bühne mit kurzer Seite 1080: kurze Seite × 1080 / kurze Seite der Bühne. Unter 400 Bezugspixeln ist der Rahmen compact, sonst regular. Eine 4K-Szene hat doppelt so viele Pixel, ihr Widget ist deshalb nicht größer: 600 Pixel auf einer 2160 hohen Bühne sind 300 Bezugspixel. Wer feiner stufen will, übergibt eigene Stufen, etwa useWidgetFormat({ sizes: [{ size: 'tiny', from: 0 }, { size: 'small', from: 200 }, { size: 'large', from: 600 }] }). Eigene Schwellen (thresholds) gehen auch, sind aber die Ausnahme: Dieselbe Rahmengröße soll in jeder App dasselbe heißen.
Der Passt-Bericht. Blendet ein Widget einen eingeschalteten Inhalt aus, weil der Rahmen zu klein ist, meldet es die Teile mit den Schlüsseln der Config-Felder, die sie einschalten, bei jeder Anordnung die vollständige Liste und eine leere, sobald alles zu sehen ist:
const { shape, size } = useWidgetFormat();
const { reportHidden } = useFitReport();
const drawsDaily = config.showDaily && size === 'regular' && shape !== 'band';
useEffect(() => {
reportHidden(config.showDaily && !drawsDaily ? ['showDaily'] : []);
}, [reportHidden, config.showDaily, drawsDaily]);
Der Editor zeigt daraus unter der Vorschau „In diesem Rahmen ausgeblendet: Nächste Tage.“ mit der Beschriftung des Felds aus der Formular-Beschreibung und unter dem Schalter einen Satz, warum er gerade nichts bewirkt. Ein Teil ohne passendes Feld erscheint mit seinem Schlüssel. Die Passt-Prüfung lässt eine Vorlage scheitern, die in ihrer eigenen Größe etwas ausblendet. Auf dem Display und in Miniaturen bleibt die Meldung ohne Wirkung; ein Widget meldet deshalb immer, ohne zu fragen, wo es läuft. Den Bericht sammelt der SceneRenderer im Modus editor und gibt ihn über onFitReport(widgetInstanceId, hiddenParts) an den Host.
Dieselbe Rechnung ohne React steht als classifyWidgetFormat({ width, height }, { canvas, previous?, thresholds?, sizes? }) in @szenera/widget-foundation/widget-ui/tokens, für Aufnahme-Werkzeug, Tests und Server. useElementSize bleibt für innere Bereiche, deren Maße erst das Layout ergibt.
Anzeige-Bausteine
Was mehrere Apps brauchen, steht einmal in @szenera/widget-foundation/widget-ui. Die Bausteine tragen keinen eigenen Stil, sie nehmen Fläche, Schrift und Maße der App. Sie kamen mit der Fassung 1.2.0 des Host-Vertrags in die Anzeige, eine App, die einen davon nutzt, verlangt deshalb ^1.2.0 (siehe Host-Vertrag und Freigabe).
| Baustein | Wofür | Wann nehmen |
|---|---|---|
PagedList |
Einträge auf Seiten statt schrumpfender Schrift: Die Liste hält die Mindestschrift ein, verteilt, was darüber nicht passt, auf Seiten im festen Takt von zehn Sekunden mit Seitenanzeige „1/3“ und zeigt bei motion: frozen die erste Seite. Gemessen wird jeder Eintrag in einer unsichtbaren Messschicht. |
Sobald eine Anzeige eine Folge gleichartiger Einträge zeigt, deren Zahl die App nicht bestimmt: Termine, Gerichte, Schlagzeilen, Notdienste. Eine feste Zahl von Teilen ordnet die App selbst an. |
QuietState |
Am Display die Fläche und höchstens ein neutraler Satz, im Editor die Erklärung mit dem nächsten Schritt. | Immer, wenn eine Anzeige keine Daten hat: Quelle leer, Abruf gescheitert, Pflichtfeld fehlt. Der Zustand empty zeigt genau diesen Baustein. |
CompanionQr |
Ein Code mit Beschriftung, der auf Displays scannt: Ruhezone von vier Modulen, eine Codefläche, die nie durchsichtig ist, Kontrast der Module, ganze Bildpunkte je Modul, Fehlerkorrektur M. Die Beschriftung schiebt den Code nie aus seiner Ruhezone. | Wann immer eine App einen Code zeigt, als Ziel eines Links, eines Wegs oder eines Gäste-WLANs. |
MarqueeBand |
Das Laufband: Naht zwischen Ende und Anfang, Tempo nach Breite in Bezugspixeln, Kantenweiche, Unterlängen, die nicht gekappt werden, und Bewegung nur bei motion: animated. Es trägt data-fit-scroll, solange es läuft. |
Für eine einzelne Zeile in einer Leiste, die länger ist als die Leiste. Mehrere Zeilen untereinander blättert PagedList. |
SourceCredit |
Die Quellenangabe fremder Daten, als Text in der Schrift der App, nie abschaltbar, nie gekürzt (data-fit-whole), nie unter 10 Bezugspixeln. |
In jeder Anzeige mit Daten von außen, in jeder Form, bei gemischten Quellen an jeder Meldung. Ein fremdes Logo oder eine Hausfarbe gehört nie dazu. |
ExternalFrame |
Der verwaltete Rahmen für fremde Inhalte: eine Seite über ihre Adresse oder ein Embed-Code. Sperre für das Gerät und die Anzeige, Sandbox-Stufen (site, code, scripted-code) ohne Formulare, Fenster und Navigation, Rechte nur aus einer festen Liste, Ansicht, Ausschnitt und langsames Scrollen per Transform, eine eigene Anordnung der App (arrange), doppelt gepuffertes Neuladen im Takt oder auf Anstoß (reloadKey), bei Ausfall der letzte Stand dezent unscharf mit Hinweis, Neuaufbau, sobald das Netz zurück ist. Eingefroren zeigt er still. |
Für jede fremde Seite, jeden Embed-Code und jeden Player in einem Rahmen, nie ein eigenes <iframe>. Apps, die mit ihrem Inhalt sprechen (ein Player), bekommen den sichtbaren Rahmen über onFrame. |
Die zwei Bündel
| Bündel | Einstieg | Was es registriert |
|---|---|---|
| runtime (Player) | registerWidgets(registry) |
Je Widget-Typ die Render-Komponente, lazy geladen — registry.register('clock', lazy(() => import('../widgets/clock/runtime'))). |
| authoring (Editor) | registerAuthoring(registry) |
Je Widget-Typ das Konfigurationsformular — registry.registerConfigForm('clock', ClockConfigForm). |
Der Renderer lädt das runtime-Bündel vom Gerät und lässt es seine Widgets in die Registry eintragen; scheitert der Import, meldet er widget.bundle.load-failed, statt die Szene abzubrechen. Das authoring-Bündel lädt der Szenen-Editor des Portals.
Beispiel: die Uhr (gekürzt)
import { defineWidget } from '@szenera/apps-sdk/widget';
import { type ClockConfig, ClockConfigSchema } from './config';
const DEFAULT_CONFIG: ClockConfig = {
variant: 'digital',
composition: 'stapel',
timeFormat: 'HH:mm',
showDate: true,
timezone: '',
theme: 'redaktion',
accentColor: '#0f766e',
// … weitere Felder, jedes mit Vorgabe im Schema …
};
export const clockWidget = defineWidget({
id: 'clock',
appId: 'clock',
name: 'Uhrzeit',
description: 'Digitale Uhr oder analoges Zifferblatt, mit optionalem Datum und eigener Zeitzone.',
configSchema: ClockConfigSchema,
noResolver: true,
preview: {
thumbnailUrl: '/apps/clock/thumbnails/clock.png',
defaultConfig: DEFAULT_CONFIG,
mockData: DEFAULT_CONFIG,
variants: [
{ id: 'digital', size: { w: 600, h: 300 }, label: 'Digital', description: 'Große Ziffern, die sich der Fläche anpassen, darunter wahlweise das Datum.', primary: true },
{ id: 'band', size: { w: 480, h: 300 }, label: 'Band', config: { composition: 'band', theme: 'kachel', dateWeekday: 'short' } },
{ id: 'analog', size: { w: 560, h: 480 }, label: 'Analog', config: { variant: 'analog', theme: 'kachel', dialNumerals: 'quarters' } },
{ id: 'hochkant', size: { w: 440, h: 720 }, label: 'Hochkant', description: 'Dieselbe Uhr auf einem stehenden Display.' },
],
},
});
Das Konfigurationsschema dazu ist ein Zod-Objekt, in dem jedes Feld eine Vorgabe und einen Text trägt — etwa variant: z.enum(['digital', 'analog']).default('digital').describe('Variante|„digital" ist die Zahlen-Uhr, „analog" ein gezeichnetes Zifferblatt.'). Unbekannte Schlüssel verwirft das Schema still, damit ältere gespeicherte Szenen weiter parsen.
Weiterführendes
- Das Manifest — wo die Widget-Definition eingetragen wird.
- Server-Teil: Resolver, Fetcher und Jobs — der Resolver, den
noResolver: falseverlangt. - Die Beispiel-Apps — Kategorie, Klasse und Bedingungen jeder App.
- Signieren und der Weg in den Katalog — wie die fertige Definition über
GET /v1/widget-catalogwieder herauskommt. - Widgets konfigurieren — wie ein Client das erzeugte Schema liest und seine Konfiguration dagegen prüft.