Studio
App entwickeln

Web-Apps paketieren, die in Studio und Player laufen.

ScreenWay-Apps sind versionierte Bundles aus Studio-Storage. Sie laufen im iFrame, erhalten ihre Konfiguration über window.APP_CONFIG und werden neben Designer-Projekten, Programmen und Notifications angezeigt.

Mit dem offiziellen Skeleton starten

Universal App Scaler, postMessage-verdrahtete config.html, deutsche und englische Locales sowie das Safe-Default-Boot-Pattern, zum Kopieren bereit.

app-skeleton.zip laden

Was eine App ist

Eine moderne App liefert mindestens index.html (die Runtime), config.html (das in-Studio-Settings-Formular), ein app.json-Schema, ein icon.svg und ein Pflicht-locales/de.json. Banner- und Featured-Bilder werden separat erzeugt und sind vor dem ersten Upload erforderlich.

Runtime

index.html rendert im Studio-Designer, im Player und in der Standalone-Vorschau. Designgröße: 1920×1080, automatisch auf jeden Viewport skaliert.

Settings-UI

config.html rendert im Studio-iFrame und kommuniziert per postMessage mit Studio. Kein äußerer Card, kein App-Header. Das liefert Studio.

Lokalisierung

Alle sichtbaren Texte liegen in locales/<code>.json. de.json ist der universelle Fallback und muss jeden Schlüssel enthalten.

Bundle-Aufbau

Ordnerstruktur
my-app/
├── app.json                # Pflicht: Schema, kein Anzeige-Text
├── index.html              # Pflicht: die App selbst (1920×1080)
├── config.html             # Pflicht: Konfigurations-UI im Studio-iFrame
├── icon.svg                # Pflicht: 512×512 Brand-Symbol
├── locales/
│   ├── de.json             # Pflicht: primärer Fallback-Locale
│   └── en.json             # Optional: weiterer Locale
├── build.sh                # im Skeleton enthalten: paketiert den Ordner
├── README.md               # im Skeleton enthalten: wird aus dem Upload-ZIP exkludiert
├── banner.png              # 1200×400, für App-Store-Sichtbarkeit erforderlich
├── banner.en.png           # Optional: lokalisierter Banner pro Locale
├── featured.png            # 1920×1080, für App-Store-Sichtbarkeit erforderlich
├── featured.en.png         # Optional: lokalisiertes Featured-Bild
├── mobile.html             # Optional: QR-Code-Controller-View
└── assets/                 # Optional: Bilder, Videos, Fonts, Audio
    ├── images/
    ├── videos/
    └── fonts/
  • index.html: Runtime-Einstieg
  • config.html: in-Studio-Settings-Formular, kein äußerer Card
  • app.json: technische Metadaten und config_schema
  • icon.svg: 512×512 Brand-Symbol, ohne Wortmarke
  • locales/de.json: primärer Fallback, enthält jeden sichtbaren String
  • banner.png (1200×400) und featured.png (1920×1080) für App-Store-Sichtbarkeit
  • build.sh + README.md liegen im Skeleton und werden aus dem Upload-ZIP exkludiert
  • mobile.html und assets/ sind optional und fügen sich um die Pflichtdateien

Flaches Bundle, kein Eltern-Ordner

Das ZIP muss die Dateien im Root enthalten, nicht innerhalb eines Verzeichnisses. Das Skeleton-eigene build.sh übernimmt das automatisch und exkludiert build.sh und README.md aus dem Upload.

app.json

app.json ist das stabile technische Schema. Anzeige-Texte bleiben in den Locale-Files. Niemals Label, Beschreibung oder Option-Titel hier einsetzen.

app.json
{
  "name": "Lobby Weather",
  "slug": "lobby-weather",
  "version": "1.0.0",
  "category": "utility",
  "is_free": true,
  "price_monthly": 0,
  "config_schema": {
    "fields": [
      { "key": "title", "type": "text",   "default": "Berlin", "required": true },
      { "key": "fontSize", "type": "number", "default": 120, "min": 24, "max": 320 },
      { "key": "accent", "type": "color", "default": "#0EA5E9" },
      {
        "key": "locale",
        "type": "select",
        "default": "de",
        "options": [{ "value": "de" }, { "value": "en" }]
      }
    ]
  }
}

Unterstützte Field-Typen

config_schema.fields[].type

text
string
Einzeiliger Text.
textarea
string
Mehrzeiliger Text, Umbrüche bleiben erhalten.
number
number
Zahleneingabe. Beachtet min und max.
url
string
URL-Feld. Browser-Validierung beim Editieren.
password
string
Maskierter Text. Für API-Keys und pro-Instanz-Geheimnisse.
color
string
Hex-String, z. B. #0EA5E9.
select
string
Einer von options[].value. Lokalisierte Labels liegen im Locale-File.
checkbox
boolean
Boolean-Toggle.

locale ist ein reservierter Key

Ein Feld mit key: "locale" steuert den Display-Locale. Sein Wert wird zu window.APP_CONFIG.locale.code und entscheidet, welche runtime.*-Strings Studio injiziert.

Lokalisierung

Studio merged locales/<chosen>.json über locales/de.json. Fehlende Keys fallen automatisch auf Deutsch zurück. Apps müssen de.json vollständig halten.

locales/de.json
{
  "description": "Live-Wetter und Vorhersage für jede Stadt.",
  "featured": {
    "title": "Immer aktuelles Wetter auf jedem Screen",
    "description": "Stadt wählen, Akzentfarbe setzen, auf jedem Display einsetzen."
  },
  "config_schema": {
    "fields": {
      "title":    { "label": "Stadt",       "placeholder": "Berlin" },
      "fontSize": { "label": "Schriftgröße" },
      "accent":   { "label": "Akzentfarbe" }
    }
  },
  "runtime": {
    "today": "Heute",
    "fallback": { "title": "Wetter", "subtitle": "Stadt in den App-Einstellungen festlegen." }
  }
}

Locale-Resolution-Chain

  1. Hat die Instance-Config locale gesetzt, nutzt Studio diesen Wert.
  2. Sonst greift der App-Store-Locale des Studio-Users (die Sprache, in der er die Apps durchstöbert hat).
  3. Sonst gewinnt de.

Runtime: window.APP_CONFIG

Studio injiziert window.APP_CONFIG am <script id="app-config"></script> -Platzhalter, bevor Ihr App-Script läuft. Read-only behandeln.

Runtime-Contract
interface AppConfig {
  instanceId: string       // UUID dieser konfigurierten Instanz
  instanceName: string     // benutzerdefinierter Anzeigename
  appId: string            // App-UUID
  appSlug: string          // z. B. "lobby-weather"
  appName: string          // z. B. "Lobby Weather"
  version: string          // z. B. "v1.0.0"
  config: Record<string, unknown>   // Werte gemäß config_schema
  locale: {
    code: string           // aufgelöster BCP-47 (z. B. "de", "en", "de-CH")
    strings: Record<string, unknown> // runtime.* gemerged mit de.json runtime.*
  }
  mobileUrl?: string       // nur gesetzt, wenn das Bundle mobile.html mitliefert
}
  • Immer defensiv lesen: var cfg = (window.APP_CONFIG && window.APP_CONFIG.config) || {}
  • Auf Inline-Defaults zurückfallen, damit lokale Entwicklung ohne Studio funktioniert
  • Runtime-Strings aus window.APP_CONFIG.locale.strings lesen
  • Intl.DateTimeFormat mit locale.code für Datumsangaben und Zahlen nutzen
  • document.body.style.opacity = "1" erst nach Anwenden der Config setzen, um Flicker zu vermeiden
  • transparentBackground in der Config bedeutet: App rendert über transparentem Body
  • mobileUrl ist nur gesetzt, wenn das Bundle mobile.html mitliefert. Vorher prüfen

Nur relative Asset-Pfade verwenden

Studio injiziert ein <base>-Tag, damit relative Pfade auf den Storage-Pfad auflösen. <img src="assets/logo.png"> funktioniert; <img src="/assets/logo.png"> bricht in Produktion. Gleiches gilt für Stylesheets, Scripts und fetch-URLs für gebündelte Daten.

Canvas-mode vs Fluid-mode

Das Skeleton liefert standardmäßig im Canvas-mode aus. Die Alternative ist sinnvoll, wenn Inhalte reflowen sollen.

Canvas-mode (Default)

Festes 1920×1080-Design, alle Größen in Pixeln. Der Universal App Scaler transformiert das Design per CSS auf den realen Viewport und hält das Seitenverhältnis bei.

Wann

Designer-Mockups sollen 1:1 mappen, das Layout besteht aus festen Regionen, Reflow auf engen Viewports ist kein Thema.

Fluid-mode (Alternative)

100vw / 100vh-Root, Größen über clamp() und Viewport-Units. Kein Scaler. Der Browser skaliert nativ.

Wann

Text soll reflowen, das Layout passt sich verschiedenen Seitenverhältnissen an, oder die App nutzt bereits ein fluides CSS-System.

Wechsel von Canvas zu Fluid

In index.html nach BEGIN UNIVERSAL APP SCALER suchen und alles bis einschließlich END UNIVERSAL APP SCALER löschen. html, body width/height von 1920px / 1080px auf 100vw / 100vh ändern, Pixelwerte auf clamp() umstellen. Der app-config-Platzhalter, getConfig() und der postMessage-Handshake bleiben identisch.

config.html (das Settings-Formular)

Studio rendert config.html in einem iFrame und übernimmt den umgebenden Card, den Titel und den Speichern-Button. Das Formular kommuniziert per postMessage mit Studio.

postMessage-Handshake
// config.html bootet im Studio-iFrame.
// postMessage zum Lesen und Schreiben der Config-Werte.

// 1) Studio mitteilen, dass wir bereit sind
window.parent.postMessage({ type: 'CONFIG_READY' }, '*')

// 2) Studio antwortet mit der gespeicherten Config
window.addEventListener('message', (event) => {
  if (event.data?.type === 'LOAD_CONFIG') {
    applyToForm(event.data.config)
  }
})

// 3) Bei jeder Änderung Config zurückschicken
function emitChange(config) {
  window.parent.postMessage({ type: 'CONFIG_CHANGED', config }, '*')
}

Message-Typen

CONFIG_READY
Child → Parent
Wird einmal gesendet, wenn config.html fertig gebootet hat.
LOAD_CONFIG
Parent → Child
Studio liefert die gespeicherte Config, damit das Formular Inputs befüllt.
CONFIG_CHANGED
Child → Parent
Bei jeder Formular-Änderung. Studio aktualisiert die Vorschau und speichert beim Klick auf „Speichern".

Config-UI-Standard befolgen

Nutzen Sie das geteilte Row-Card-Pattern aus der config.html des Skeletons: kein äußerer Card, kein App-Header, kein Logo, keine eigenen Fonts. Pro App ändert sich nur die --accent-CSS-Custom-Property.

Große Configs: über die API laden

URL-kodierte Configs sind durch den Browser begrenzt. Bei Apps mit langen Arrays (Quiz-Fragen, Ticker-Items, große Medien-Listen) holen Sie die gespeicherte Config direkt von /api/app-instances/{instanceId}/config statt sich auf URL-Parameter zu verlassen.

API-Fallback-Loader
async function loadConfig() {
  const urlParams = new URLSearchParams(window.location.search)
  const instanceId = urlParams.get('instanceId') || window.APP_CONFIG?.instanceId

  if (instanceId) {
    const res = await fetch(`/api/app-instances/${instanceId}/config`)
    if (res.ok) {
      const { config } = await res.json()
      return config
    }
  }

  return window.APP_CONFIG?.config ?? {}
}

Komplexe Felder in config.html serialisieren

Verschachtelte Arrays und Objekte als JSON-String im CONFIG_CHANGED-Payload senden und beim Laden wieder parsen. So funktioniert jeder Transport-Pfad (URL, postMessage, API).

Browser-Kompatibilität

Player-Geräte umfassen Android-Sticks, Smart-TVs und Embedded-Screens. Baseline: Chrome 66 / ES2017. Der Universal App Scaler ist bewusst in schlichtem ES5/ES2017 geschrieben, damit er überall läuft.

ES2017-Features nutzen

async/await, Spread, Destructuring sind ok. Optional Chaining und Nullish Coalescing im Scaler-/Boot-Code besser meiden. Innerhalb nachgeladener Helper sind sie fein.

Keine Bundler nötig

Apps sind reines HTML/JS/CSS. Bundler weglassen, außer Ihre App braucht sie wirklich. Der Katalog bevorzugt self-contained Dateien.

Auf einem TV-Stick testen

Mobile-Chrome ist gnädig. Hardware-Sticks (Android-TV, Tizen) zeigen wirklich, wo Performance- und Font-Probleme stecken.

Lokale Entwicklung

Beliebigen statischen File-Server vom Bundle-Root starten und index.html öffnen. Ohne APP_CONFIG greifen Ihre Inline-Defaults. Von Tag eins für diesen Pfad designen.

lokaler Server
cd my-app
python3 -m http.server 8000
# index.html  → http://localhost:8000/
# config.html → http://localhost:8000/config.html

Lokal die Config per URL-Parameter steuern. Das Skeleton parst sie zurück in dieselbe Shape wie APP_CONFIG.config:

URL-Config
http://localhost:8000/?title=Test&fontSize=200&transparentBackground=true

Debugging

console.log mit App-Kontext

Logs mit [${APP_CONFIG?.appName ?? 'dev'}] präfixen. Screen-Logs bleiben durchsuchbar.

Dev vs. Produktion erkennen

!!window.APP_CONFIG ist der sicherste Produktions-Check. So überspringen Sie Mock-Daten auf echten Screens.

postMessage tracen

Jede CONFIG_*-Message in config.html während der Entwicklung loggen. Studio verwirft fehlerhafte Payloads stillschweigend.

Accent + Theme-Drift

Akzentfarbe aus einer Quelle lesen (Config oder Default), alles andere (Focus-Ringe, Callouts) daraus ableiten. Keine hartkodierte Markenfarbe sonst im Formular.

Store-Assets

Studio zeigt für jede App zwei Bilder: im Katalog-Grid, auf der App-Detailseite und in der Instance-Vorschau. Beide Dateien liegen im Bundle-Root neben app.json.

Bilddateien

banner.pngrequired
1200×400, PNG
Katalog-Karte und Listen-Thumbnail.
featured.pngrequired
1920×1080, PNG
Hero auf der Detailseite, wenn die App featured ist.
banner.<lang>.png
1200×400, PNG
Lokalisierte Variante pro Locale (z. B. banner.en.png).
featured.<lang>.png
1920×1080, PNG
Lokalisierte Variante pro Locale.

Lokalisierte Varianten fallen auf Default zurück

Eine Locale ohne eigene banner.<lang>.png nutzt automatisch banner.png. Lokalisierte Varianten nur dann mitliefern, wenn das Bild selbst sprachspezifische Inhalte hat (Text, Screenshots).

Design-Regeln

Banner (1200×400, 3:1)

Horizontales Layout, stilisierter Marketing-Mockup (kein wörtlicher Screenshot). Safe-Area-Inset 72px / 40px. Linke Hälfte: Brand-Badge, Tagline, Hero-Element. Rechte Hälfte: Feature-Tiles, Varianten-Grid oder ein zentrierter Hero. Dunkle Gradient-Basis Richtung Akzentfarbe.

Featured (1920×1080, 16:9)

Reines Bild-Asset. Studio legt Brand, Headline, Description und CTAs als UI darüber, also draußen lassen. Center-stage-Hero 3–5× größer skaliert als auf dem Banner. Gleiche Palette wie der Banner. Safe-Area-Inset 140px / 100px. Großzügig Leerraum.

Palette aus der App ableiten

Farben zuerst aus icon.svg (Gradient-Stops, Fills), dann aus config_schema-Defaults (backgroundColor, accentColor), erst zuletzt aus einer kategorie-basierten Palette. Generische Slate-Banner vermeiden, die Basis deutlich Richtung Akzentfarbe drücken.

PNGs erzeugen

  • HTML-Mockup + Chrome-Screenshot: in einem normalen Browser-Tab in exakter Größe rendern, DevTools öffnen, Device-Toolbar aktivieren, Custom-Size (1200×400 oder 1920×1080) bei DPR 1 setzen, dann Capture full size screenshot.
  • Headless via Puppeteer: page.setViewport mit deviceScaleFactor: 1, lokale HTML-Datei laden,page.screenshot.
  • Design-Tool: Figma / Sketch / Affinity in exakter Größe als PNG exportieren, DPR 1× (nicht retina-doppelt).

Publish-Flow

Studio-Admins nehmen Apps in den Katalog auf. Self-Service-Uploads sind in Vorbereitung. Bis dahin geht jeder Katalog-Eintrag über das ScreenWay-Team mit kurzer Prüfung.

1. Paketieren

app.json validieren, sicherstellen, dass locales/de.json jeden Key abdeckt, Banner- und Featured-Bilder ins Bundle-Root legen.

2. Version bumpen

./build.sh --bump-patch ausführen (oder --bump-minor / --bump-major). Das Skeleton liefert build.sh dafür mit.

3. Einreichen

ZIP an apps@screenway.com senden. Wir validieren, laden hoch und antworten mit dem Live-Link.
Build mit Skeleton-Script
# Im App-Ordner. Das Skeleton liefert build.sh mit
./build.sh                # build mit aktueller Version aus app.json
./build.sh --bump-patch   # 1.0.0 → 1.0.1
./build.sh --bump-minor   # 1.0.0 → 1.1.0
./build.sh --bump-major   # 1.0.0 → 2.0.0

# Ergebnis: my-app-v1.0.1.zip im aktuellen Verzeichnis.
# Benötigt: bash, zip, jq (brew install jq / apt install jq).
Fallback (manuelles ZIP)
# Fallback ohne bash + jq
cd my-app
zip -r ../my-app-v1.0.0.zip . \
  -x "*.DS_Store" -x "__MACOSX/*" \
  -x "build.sh" -x "README.md" -x ".git/*"

Versionierung

Semantische Versionierung

MAJOR
1.0.0 → 2.0.0
Breaking Config-Changes: umbenannte oder entfernte Felder, umstrukturiertes Layout. Bestehende Instanzen können Re-Konfiguration brauchen.
MINOR
1.0.0 → 1.1.0
Neue Features, neue optionale Config-Felder mit sinnvollen Defaults.
PATCH
1.0.0 → 1.0.1
Bugfixes, Polish, Performance, keine Config-Changes.

Upload-Validierung

  • Abgelehnt: fehlende app.json, index.html, icon.svg oder locales/de.json im ZIP-Root
  • Abgelehnt: ungültiges JSON in app.json oder einem Locale-File
  • Abgelehnt: app.json ohne name oder slug oder mit ungültigem config_schema
  • Abgelehnt: doppelter slug. Stattdessen als neue Version der bestehenden App hochladen
  • Abgelehnt: kein ZIP, kaputtes ZIP oder verschachtelter Ordner
  • Akzeptiert aber versteckt: fehlende banner.png oder featured.png. App wird hochgeladen, bleibt aber aus dem App-Store-Grid raus
  • Akzeptiert mit Fallback: fehlende config.html. Studio generiert ein einfaches Formular (visuell inkonsistent zum Katalog)

Was in die Submission-Mail gehört

App-Slug, Ziel-Locales, ein Einzeiler zur Änderung und das Bundle als ZIP-Anhang. Bei Updates die vorherige Version nennen, damit wir den Rollback-Pfad sauber halten.

Nächste Schritte

Mit dem Skeleton starten

Das aktuelle app-skeleton bringt vollständigen Scaler, postMessage-verdrahtete config.html, deutsche und englische Locales und das Safe-Default-Boot-Pattern mit. Kopieren, umbenennen, ausliefern.

v1-API für Inhalte nutzen

App-Instanzen sind erstklassige API-Ressourcen. Bauen Sie Flows, die Instanzen anlegen, ihre config aktualisieren und sie an Programme hängen, ohne Studio zu öffnen.

An Store-Assets denken

Banner und Featured-Bilder entstehen aus HTML-Templates pro App. Visual sprache an der App-internen Akzentfarbe ausrichten, damit der Store kohärent wirkt.