← mathe.digital
⬇ Markdown

Tipp: Mit Cmd+P (Mac) oder Ctrl+P (Windows/Linux) erscheint der Druckdialog → „Als PDF speichern" wählen.

Matheforscher postMessage-Protokoll

Dokumentation für Entwickler:innen, die ihre Webapp in die Matheforscher-Plattform einbinden möchten.

Kurz und gut: Deine Webapp läuft im iframe. Mit ein paar Zeilen JavaScript meldet sie der Plattform, was das Kind tut. Die Plattform nutzt das für Lehrkraft-Dashboard, automatische Lösungs-Erkennung und KI-gestützte Lernbegleitung.


Inhalt

  1. Worum geht's?
  2. Quickstart (5 Zeilen)
  3. Vollständiger Auto-Init (alles in einem)
  4. Plattform-Erkennung
  5. iframe-Berechtigungen
  6. Konfiguration via Query-Parameter
  7. App-Manifest beim ready-Event
  8. Event-Typen
  9. Schema-Beschreibung für den App-Pool
  10. Best Practices
  11. Komplettbeispiel: Mini-Rechenfeld
  12. Browser-Kompatibilität & Stolpersteine
  13. Testen + Debugging
  14. Datenschutz
  15. Versions-History

Worum geht's?

Die Matheforscher-Plattform bettet Webapps per <iframe> in Forscheraufträge ein. Apps müssen nichts „wissen" — sie funktionieren auch ohne Anbindung. Optional kann eine App aber per window.postMessage an das Parent-Fenster Events senden. Diese Events ermöglichen u.a. folgende Funktionen:

Event Plattform-Reaktion
matheforscher:progress Lehrkraft-Dashboard zeigt Zwischenschritte; Live-Feedback im Schüler-UI als Chip
matheforscher:complete Aktive Teilaufgabe wird automatisch als „fertig" markiert
matheforscher:configured Bestätigt eine Live-Re-Konfiguration der App — vermeidet iframe-Reload
matheforscher:error Plattform zeigt Fehler-Hinweis (selten benötigt)
matheforscher:action Lernverlauf wird gespeichert; KI kann Schritte nachvollziehen
matheforscher:state Aktueller App-Zustand wird gespeichert; KI kann ihn auf Anfrage interpretieren
matheforscher:highlight Plattform → App: hebt deklarierte Elemente hervor (sprachbegleitender KI-Hinweis)
matheforscher:perform Plattform → App: führt deklarierte Aktionen vor (demo) oder aus (execute)

Alle Events sind optional und additiv — du kannst klein anfangen (nur complete) und später ausbauen.


Quickstart (5 Zeilen)

Minimal-Implementierung, die der Plattform nur sagt „Aufgabe gelöst":

function notifyComplete() {
  window.parent.postMessage({ type: 'matheforscher:complete' }, '*');
}
// im UI dann: button.onclick = notifyComplete;

Das war's. Dein App ist jetzt „matheforscher-aware".

Für die Reichhaltigkeit empfehlen wir, zusätzlich state und action zu senden — siehe unten.

Noch schneller mit dem SDK: Statt das Protokoll von Hand zu implementieren, kannst du das offizielle Mini-SDK einbinden (<script src="https://mathe.digital/sdk/forscher-protokoll.js">, zero-deps, ~8 KB). Es übernimmt Embedding-Detection, hello/ready-Handshake, set-config-Acks und State-Debouncing komplett — siehe SDK-Doku bzw. die Demo unter /dev/sdk-demo.html.


Vollständiger Auto-Init (alles in einem)

Wenn du ohne Umweg gleich alle Plattform-Mechanik bedienen willst, kopiere diesen Block in deine App. Er deckt Erkennung, Hello-Antwort, Send-Helper und Screenshot-on-Request in einem ab — alle Browser, ohne weitere Konfiguration:

// 1) Plattform-Erkennung (drei redundante Signale)
let inMatheforscher =
  new URLSearchParams(location.search).get('matheforscher') === '1' ||
  location.hash.includes('matheforscher=1') ||
  window.self !== window.top;

// 2) Sicherer Send-Helper (no-op, wenn nicht eingebettet)
const send = (msg) => inMatheforscher && window.parent.postMessage(msg, '*');

// 3) Embedded-CSS-Klasse — eigene Navigation/Header ggf. ausblenden
if (inMatheforscher) document.body.classList.add('embedded');

// 4) Aktuelle Konfiguration aus URL-Parametern lesen (Beispiel — passe an)
const params = new URLSearchParams(location.search);
const config = {
  zr: parseInt(params.get('zr') || '20', 10),
  modus: params.get('modus') || 'frei'
};
const supportedParams = {
  zr:    { type: 'number', default: 20, values: [10, 20, 100] },
  modus: { type: 'string', default: 'frei', values: ['frei', 'zerlegung', 'darstellung'] }
};

// 5) Plattform-Events empfangen
window.addEventListener('message', async (e) => {
  const t = e.data?.type;
  if (typeof t !== 'string' || !t.startsWith('matheforscher:')) return;

  // 5a) Hello → mit Ready antworten (zeigt der Plattform: App ist bereit + Config)
  if (t === 'matheforscher:hello') {
    inMatheforscher = true;
    document.body.classList.add('embedded');
    e.source?.postMessage(
      { type: 'matheforscher:ready', payload: { config, supportedParams } },
      e.origin || '*'
    );
    return;
  }

  // 5b) Screenshot-Request → DOM oder Canvas der App ablichten
  if (t === 'matheforscher:request-screenshot') {
    const canvas = document.querySelector('canvas');
    let dataUrl;
    if (canvas) {
      dataUrl = canvas.toDataURL('image/png');
    } else {
      // html2canvas-pro lokal bündeln (npm i html2canvas-pro), nicht von CDN —
      // strenge CSPs blockieren CDN-Imports. Wenn deine App keine CSP setzt,
      // funktioniert auch: const html2canvas = (await import('https://cdn.jsdelivr.net/npm/html2canvas-pro@2/+esm')).default;
      const html2canvas = (await import('html2canvas-pro')).default;
      const c = await html2canvas(document.body, { useCORS: true, scale: 1 });
      dataUrl = c.toDataURL('image/png');
    }
    e.source?.postMessage(
      { type: 'matheforscher:screenshot', payload: { dataUrl } },
      e.origin || '*'
    );
  }
});

Damit funktioniert deine App in der Plattform ohne weitere Setup-Schritte — du musst nur noch deine fachlichen Events (progress, action, state, complete) per send(...) an den passenden Stellen im UI-Code aufrufen.


Plattform-Erkennung

Die Plattform sendet drei redundante Signale, sodass deine App die Einbettung auch dann erkennen kann, wenn z. B. ein Client-Side-Router (history.replaceState) den Query-String beim Bootstrap entfernt. Du brauchst nur eines davon zu prüfen — empfohlen: alle drei zusammen, dann ist es bombensicher.

Die drei Signale

Signal Wann Wo prüfen
1. Query-Parameter ?matheforscher=1 Beim ersten Laden new URLSearchParams(location.search).get('matheforscher')
2. Hash-Fragment #matheforscher=1 Beim ersten Laden, überlebt SPA-Routing location.hash.includes('matheforscher')
3. matheforscher:hello-postMessage Wiederholt nach iframe-load-Event (6× alle 500 ms) window.addEventListener('message', …)

Empfohlene Detection-Funktion

const inMatheforscher =
  new URLSearchParams(location.search).get('matheforscher') === '1' ||
  location.hash.includes('matheforscher=1') ||
  // Fallback: Wir sind in irgendeinem Iframe — kein Cross-Origin-Read nötig
  window.self !== window.top;

if (inMatheforscher) {
  document.body.classList.add('embedded');
  // ggf. eigene Navigation ausblenden, FullScreen-Mode aktivieren etc.
}

// Zusätzlich: hello-postMessage abfangen, damit auch nach Routing erkannt wird
window.addEventListener('message', (e) => {
  if (e.data?.type === 'matheforscher:hello') {
    document.body.classList.add('embedded');
    // Antworten — siehe `matheforscher:ready` weiter unten
  }
});

window.self !== window.top ist der zuverlässigste lokale Check und erlaubt — anders als window.parent.location o. ä. — keinen Cross-Origin-Read, löst also keine SecurityError-Warnung im Browser aus.

Sicher senden — nur wenn eingebettet, postMessage rufen:

function send(payload) {
  if (!inMatheforscher) return;
  window.parent.postMessage(payload, '*');
}

Hinweis zum targetOrigin: Wir empfehlen '*' für maximale Kompatibilität. Die Plattform validiert auf Empfänger-Seite, dass der Sender (event.origin) zur registrierten App-URL passt.

Anti-Pattern: kein direkter Parent-DOM-Zugriff

Diese Zeile löst in Cross-Origin-Iframes (Standard-Fall) eine SecurityError-Warnung in der Browser-Konsole aus und gibt nichts Brauchbares zurück:

// ❌ NICHT MACHEN
const isHidden = window.parent.frameElement?.style.display === 'none';
const parentUrl = window.parent.location.href;
const parentDoc = window.top.document;

Browser blockieren jeden direkten Zugriff auf Properties eines Fremd-Origin-Fensters. Verwende stattdessen postMessage und/oder die Signale oben — die funktionieren ohne Sicherheitswarnung.


iframe-Berechtigungen

Die Plattform bettet deine App so ein:

<iframe
  src="…?matheforscher=1#matheforscher=1"
  allow="microphone; camera; clipboard-write; fullscreen"
></iframe>

Daraus folgt für dich konkret:

Funktion Funktioniert in deiner App?
getUserMedia für Mikrofon ✅ ja, mit User-Geste
getUserMedia für Kamera ✅ ja, mit User-Geste
navigator.clipboard.writeText(...) ✅ ja
element.requestFullscreen() ✅ ja
localStorage / sessionStorage / Cookies (auf deiner Origin) ✅ ja, normal nutzbar
fetch zu deiner eigenen Origin ✅ ja
fetch zu Drittanbieter-APIs ✅ ja, sofern CORS passt
Cross-Origin-Zugriff aufs Parent-Fenster ❌ nein (Same-Origin-Policy)
display-capture (Screen-Sharing), payment, usb, geolocation etc. ❌ nicht freigeschaltet

Es ist kein sandbox-Attribut gesetzt, deine App läuft also unrestricted. Wenn du eine zusätzliche Permission im allow= brauchst (z. B. geolocation), nimm Kontakt auf — wir ergänzen sie.


Konfiguration via Query-Parameter

Im App-Pool kann die Lehrkraft pro App Default-Parameter hinterlegen (z.B. zr=20, farbe=blau). Diese werden beim Einbetten als Query-Parameter an deine App-URL angehängt — gemeinsam mit dem matheforscher=1-Marker.

Deine App liest sie via URLSearchParams:

const params = new URLSearchParams(location.search);
const zr = parseInt(params.get('zr') || '20', 10);
const farbe = params.get('farbe') || 'blau';
// initialisiere die App entsprechend

Was du dokumentieren solltest:

  • In deiner App-Pool-Beschreibung (Feld „Was kann das Kind in der App tun?"): liste die unterstützten Parameter mit Default-Werten und gültigen Wertebereichen.
  • Beispiel-Block für die App-Pool-Beschreibung:
## Unterstützte URL-Parameter
- `zr` (Zahl, default 20): Zahlraum (10, 20, 100)
- `farbe` (String, default 'blau'): Akzentfarbe ('blau', 'rot', 'grün')
- `zeigeHilfe` (Boolean, default false): blendet Hilfe-Tooltips ein
- `modus` (String, default 'frei'): 'frei' | 'zerlegung' | 'darstellung'

So kann die Lehrkraft beim Auftrag-Erstellen die App passend konfigurieren — und die KI hat im Generator-Modal Kontext, was möglich ist.

Auto-Config beim Start: Damit die Plattform (und die KI im State-Check) weiß, mit welcher Konfiguration deine App läuft, sende beim Start ein matheforscher:ready-Event (siehe nächste Sektion). So muss die Lehrkraft nicht raten, ob ihre Parameter angekommen sind.


App-Manifest beim ready-Event

Eine App kann sich beim ersten matheforscher:ready selbst beschreiben. Die Plattform schlägt die gemeldeten Felder beim Anlegen der App im App-Pool automatisch vor — die Lehrkraft kann jeden Wert vor dem Speichern überschreiben. Bei späteren App-Updates lässt sich das Manifest per Klick neu abrufen; die Plattform vergleicht es per SHA-256-Hash mit dem zuletzt adoptierten Stand und zeigt die geänderten Felder zur Pro-Feld-Übernahme an.

Manifest-Payload

window.parent.postMessage({
  type: 'matheforscher:ready',
  payload: {
    config: { /* aktueller Lauf, wie bisher */ },
    supportedParams: { /* wie bisher — Schema-Definition */ },
    manifest: {                                   // NEU, alles optional
      name: 'Mini-Rechenfeld',
      description: 'Plättchen ins Zwanzigerfeld legen — Zahlzerlegungen sehen.',
      kindActions: 'Plättchen setzen/entfernen, Reset, Anzahl ablesen.',
      categories: ['Arithmetik', 'Zahlzerlegung'],
      stateSchema: 'state.felder als 2D-Array (0=leer, 1=blau)…',
      defaultParams: { zr: '20', modus: 'frei' },
      iconUrl: '/icon.png',                       // relativ oder absolut
      infoUrl: 'https://urff.app/rechenfeld/doku',
      version: '1.4.0',                           // Freitext (semver oder ISO-Datum)
      highlightTargets: [                           // NEU: hervorhebbare Elemente
        { id: 'summe', label: 'die Summenanzeige oben' }
      ],
      performableActions: [                         // NEU: fern-aufrufbare Aktionen
        { action: 'place', target: 'zelle', label: 'eine Zelle füllen', payloadSchema: 'payload {row,col}' },
        { action: 'reset', label: 'das Feld leeren' }
      ]
    }
  }
}, e.origin || '*');

Felder

Feld Typ Bedeutung
name string Anzeigename im App-Pool
description string 1–2 Sätze, was die App didaktisch leistet
kindActions string „Was kann das Kind in der App tun?" — Prosa für Lehrkräfte und KI
categories string[] Themenbereiche (z.B. „Arithmetik", „Stellenwert")
stateSchema string Prosa-Beschreibung des per matheforscher:state gesendeten App-Zustands, damit die KI ihn interpretieren kann
defaultParams Record<string,string> Empfohlene Default-Werte als key=value-Paare
supportedParams ParamSchema Wie heute (kann auch in payload.supportedParams außerhalb von manifest stehen — beide werden akzeptiert)
iconUrl string App-Icon (absolut oder relativ zur App-URL)
infoUrl string Doku-Seite / About-Page für die App
version string Freitext-Version, hilft Entwickler:innen beim Tracking — die Plattform nutzt aber primär einen Inhalts-Hash
highlightTargets HighlightTarget[] Vom Arbeitsmittel hervorhebbare Elemente, je { id, label }. Nicht-leer = App unterstützt matheforscher:highlight.
performableActions PerformableAction[] Fern-aufrufbare Aktionen, je { action, target?, label, payloadSchema? }. Nicht-leer = App unterstützt matheforscher:perform.
didaktik object v2.9 — maschinenlesbare didaktische Selbstauskunft: { klassenstufen?: number[] /* 1–4 */, zahlenraeume?: string[] /* 'ZR10'…'ZR1M' */, darstellungsebenen?: string[] /* 'enaktiv'|'ikonisch'|'symbolisch' */, inhaltsbereiche?: string[], kompetenzen?: string[] }. Speist Auftrags-Generator, App-Passungs-Check und Bibliotheks-Filter.
stateSchemaV2 object v2.9 — strukturiertes State-Schema: { jsonSchema?: object /* echtes JSON-Schema */, prosa?: string /* KI-Interpretationshilfe */, beispiele?: Array<{ state, bedeutung: string }> /* Few-Shot-Paare für die KI, max 5 */ }. Ergänzt (ersetzt nicht) das Prosa-stateSchema.
protocolLevel number v2.9 — selbst-deklariertes Conformance-Level: 0 = nur iframe · 1 = konfigurierbar (ready+supportedParams) · 2 = beobachtbar (state+action) · 3 = KI-integriert (beschreibung, highlight/perform) · 4 = integriert (context, ergebnis, report).

Regeln

  • Jedes Feld ist optional. Ein nicht-vorhandener Key bedeutet „App macht keine Aussage"; ein leerer Wert bedeutet „App meldet bewusst Leere".
  • Apps können das Manifest beim regulären matheforscher:ready mitsenden oder nur als Antwort auf einen matheforscher:discover-Ping liefern — die Plattform akzeptiert beides.
  • Bestehende Apps, die nur supportedParams ohne manifest schicken, funktionieren unverändert weiter.

Quickstart

Wenn deine App noch gar nichts implementiert hat, reicht dieser Block:

window.addEventListener('message', (e) => {
  if (e.data?.type === 'matheforscher:hello' || e.data?.type === 'matheforscher:discover') {
    e.source?.postMessage({
      type: 'matheforscher:ready',
      payload: {
        manifest: {
          name: 'Deine App',
          description: 'Kurze Beschreibung in 1–2 Sätzen.',
          kindActions: 'Was die Kinder tun können.',
          categories: ['Arithmetik'],
          version: '1.0.0'
        }
      }
    }, e.origin || '*');
  }
});

Event-Typen

Alle Events sind JavaScript-Objekte mit einem type-Feld, das mit 'matheforscher:' beginnt. Andere Felder sind event-spezifisch.

matheforscher:progress

Wann senden: Bei jedem konstruktiven Zwischenschritt, der für die Lehrkraft sichtbar sein sollte.

send({
  type: 'matheforscher:progress',
  teilaufgabeHinweis: '10 = 7 + 3',   // Pflicht: kurze Beschreibung des Fortschritts
  payload: { /* optional, app-spezifisch */ }
});

Plattform-Effekt: Im Schüler-UI erscheint ein kleiner grüner Chip („✓ 10 = 7 + 3"). Im Lehrkraft-Dashboard wird der Hinweis im Aktivitätslog angezeigt.

Beispiele:

  • Eine Zahlzerlegung wurde gefunden: '10 = 7 + 3'
  • Eine Form wurde korrekt gelegt: 'Quadrat aus 4 Plättchen'
  • Eine Aufgabe wurde richtig gerechnet: '17 + 8 = 25'

matheforscher:ready

Wann senden: Genau einmal beim App-Start, sobald deine App ihre Konfiguration aus den URL-Parametern eingelesen und initialisiert hat.

send({
  type: 'matheforscher:ready',
  payload: {
    config: {
      // die aktuelle Konfiguration deiner App, nachdem sie die URL-Parameter angewandt hat
      zr: 20,
      farbe: 'blau',
      modus: 'zerlegung'
    },
    supportedParams: {
      // Schema deiner unterstützten Parameter (gibt der Plattform/KI Kontext für künftige Aufträge)
      zr:           { type: 'number', default: 20, values: [10, 20, 100] },
      farbe:        { type: 'string', default: 'blau', values: ['blau', 'rot', 'grün'] },
      zeigeHilfe:   { type: 'boolean', default: false },
      modus:        { type: 'string', default: 'frei', values: ['frei', 'zerlegung', 'darstellung'] }
    }
  }
});

Plattform-Effekt:

  • Die Plattform speichert config und supportedParams im Schüler-Store.
  • Beim KI-Check bekommt die KI die aktuelle App-Config als Kontext — kann das Kind besser einschätzen.
  • Im Lehrkraft-Dashboard kann später angezeigt werden, womit die App konkret läuft (zur Verifikation).

Wichtig: payload.config und payload.supportedParams sind beide optional, aber empfohlen. Wenn deine App keine Parameter unterstützt: sende { type: 'matheforscher:ready' } ohne Payload — die Plattform weiß dann zumindest, dass die App initialisiert ist.

matheforscher:complete

Semantik: Informativ, nicht autoritativ. Die Plattform erkennt Erfolg primär plattformseitig über state + Aktionen + die von der Lehrkraft definierte Erfolgs-Bedingung — nicht allein über dieses Event. complete ist ein optionaler Hinweis deiner App, kein verbindliches Abschluss-Signal.

Wann senden: Nur wenn deine App selbst mit Sicherheit weiß, dass die Aufgabe abgeschlossen ist (z.B. alle Felder korrekt ausgefüllt, Zielzahl exakt erreicht). In allen anderen Fällen reicht state + action — die Plattform entscheidet über Erfolg anhand der KI und der Erfolgs-Bedingung der Lehrkraft.

send({
  type: 'matheforscher:complete',
  payload: { /* optional, z.B. Endstand */ }
});

Plattform-Effekt: Die aktuell aktive Teilaufgabe wird automatisch als „fertig" markiert (Häkchen). Spart dem Kind den manuellen „Fertig"-Klick — aber nur, wenn die App tatsächlich sicher ist.

Hinweis zur plattformseitigen Erfolgs-Erkennung: Lehrkräfte können pro Auftrag eine textuelle Erfolgs-Bedingung definieren (z.B. „Alle elf Zerlegungen der 10 sind dargestellt"). Wenn das Kind auf „✅ Bin ich fertig?" klickt, prüft die KI anhand des aktuellen state und der letzten Aktionen, ob diese Bedingung erfüllt ist — unabhängig davon, ob die App ein complete-Event gesendet hat.

matheforscher:error

Wann senden: Bei App-internen Fehlern, die das Kind nicht selbst beheben kann.

send({
  type: 'matheforscher:error',
  message: 'Konnte Daten nicht laden'
});

Plattform-Effekt: Fehler-Banner im Schüler-UI. Selten nötig — meistens reicht App-internes Error-Handling.

matheforscher:action

Wann senden: Bei jeder bedeutsamen Schüler-Handlung — Plättchen setzen, Karte umdrehen, Würfel werfen, Eingabe machen.

send({
  type: 'matheforscher:action',
  action: 'place',                    // kurzer Verb-Code
  target: 'plättchen',                // optional: was wurde manipuliert
  payload: { row: 3, col: 5, color: 'rot' },  // optional: app-spezifische Details
  beschreibung: 'Kind legt ein rotes Plättchen in Zeile 4, Spalte 6'  // optional, ab v2.9 — siehe unten
});

Plattform-Effekt: Action wird im Schüler-Store gespeichert (max 200 in Memory, letzte 50 in LocalStorage). Wenn das Kind den „🤖 Wie weit bin ich?"-Button drückt, schickt die Plattform die letzten 20 Actions an die KI als Kontext.

beschreibung (optional, empfohlen — semantische Selbstbeschreibung): Ein kurzer deutscher Satz, was die Handlung bedeutet. Deine App kennt ihre Semantik am besten — mit beschreibung muss die Plattform-KI dein Roh-payload nicht mehr deuten. Senden alle Actions eine beschreibung, bekommt die KI die Beschreibungen als Liste statt Roh-JSON (weniger Fehlinterpretationen, besseres Feedback fürs Kind).

Konvention für action-Codes:

Verb Bedeutung Beispiele
place etwas positionieren Plättchen, Stein, Zahl
remove etwas entfernen Plättchen weg, Eingabe löschen
move etwas bewegen Drag&Drop, Schieben
toggle An/Aus, Ja/Nein Häkchen, Markierung
set Wert setzen Eingabefeld, Slider
select etwas auswählen Karte, Option
reset Zurücksetzen Feld leeren
try Probier-Aktion Versuch, Lösungsversuch
check Prüfung anstoßen Eigenes Lösen prüfen

Halte dich locker an diese Verben — eigene gehen aber auch (z.B. 'roll' beim Würfel).

matheforscher:hello (Plattform → App)

Wann empfangen: Direkt nach dem load-Event deines Iframes, danach bis zu 6× alle 500 ms wiederholt — solange, bis deine App mit matheforscher:ready antwortet (oder die 6 Versuche aufgebraucht sind).

Format:

{ "type": "matheforscher:hello", "host": "matheforscher", "version": 1 }

Empfänger:

window.addEventListener('message', (e) => {
  if (e.data?.type !== 'matheforscher:hello') return;
  // Wir sind sicher in Matheforscher eingebettet — auch wenn URL-Parameter fehlen.
  document.body.classList.add('embedded');
  // Antworten mit `ready` — wichtig, damit das Pingen aufhört und
  // die Plattform unsere Konfiguration kennt.
  e.source?.postMessage({
    type: 'matheforscher:ready',
    payload: {
      config: { /* aktuelle App-Konfiguration */ },
      supportedParams: { /* Schema, siehe matheforscher:ready */ }
    }
  }, e.origin);
});

Warum dieses Event existiert: Manche Apps räumen den Query-String beim Bootstrap weg (Client-Side-Router). Das Hello-postMessage ist die zuverlässigste Erkennung — unabhängig von URL und Routing.

Plattform-Effekt: Sobald deine App matheforscher:ready zurücksendet, stoppt die Plattform das Hello-Pingen und speichert deine Konfiguration im Schüler-Store.

matheforscher:request-screenshot (Plattform → App)

Wann empfangen: Wenn das Kind im Schüler-UI „📸 App-Screenshot jetzt machen" drückt UND deine App nicht same-origin mit der Plattform ist.

// Empfänger in deiner App:
window.addEventListener('message', async (e) => {
  if (e.data?.type !== 'matheforscher:request-screenshot') return;
  // Dein Screenshot-Code, z.B. via html2canvas oder eigenes Canvas-Rendering
  const dataUrl = await dieAppMachtSelberEinenScreenshot();
  e.source.postMessage({
    type: 'matheforscher:screenshot',
    payload: { dataUrl }
  }, e.origin);
});

Implementierungs-Vorlagen:

a) Wenn deine App auf einem <canvas> rendert (z.B. ein eigenes Rechenfeld):

window.addEventListener('message', (e) => {
  if (e.data?.type !== 'matheforscher:request-screenshot') return;
  const canvas = document.querySelector('canvas');
  if (!canvas) return;
  const dataUrl = canvas.toDataURL('image/png');
  e.source.postMessage({
    type: 'matheforscher:screenshot',
    payload: { dataUrl }
  }, e.origin);
});

b) Wenn deine App DOM-basiert rendert (HTML/CSS):

window.addEventListener('message', async (e) => {
  if (e.data?.type !== 'matheforscher:request-screenshot') return;
  // EMPFOHLEN: html2canvas-pro lokal bündeln (`npm i html2canvas-pro`).
  // Strenge Content-Security-Policies (z. B. mit `script-src 'self'`)
  // blockieren sonst den Import von einer fremden CDN.
  const html2canvas = (await import('html2canvas-pro')).default;
  // Alternative ohne Build-Schritt (nur in Apps OHNE strenge CSP):
  // const html2canvas = (await import('https://cdn.jsdelivr.net/npm/html2canvas-pro@2/+esm')).default;
  const canvas = await html2canvas(document.body, { useCORS: true, scale: 1 });
  const dataUrl = canvas.toDataURL('image/png');
  e.source.postMessage({
    type: 'matheforscher:screenshot',
    payload: { dataUrl }
  }, e.origin);
});

c) Wenn deine App eine Mischform ist: kombiniere beide Ansätze. Für Hauptbereiche Canvas, für umrahmende UI html2canvas. Oder nimm einfach immer html2canvas.

Hinweis zu Bildgröße: Default-PNG kann groß werden. Wenn deine App nicht hochauflösend rendert, reicht JPEG mit canvas.toDataURL('image/jpeg', 0.85) — kleinere Datei, schnellerer Upload.

matheforscher:screenshot (App → Plattform)

Format: { type: 'matheforscher:screenshot', payload: { dataUrl: string } }

dataUrl ist ein Standard-Data-URL (data:image/png;base64,...). Die Plattform decodiert und lädt das Bild in Storage hoch.

Plattform-Effekt: Erscheint im Lehrkraft-Dashboard wie ein manueller Upload.

matheforscher:state

Wann senden: Nach jeder relevanten Zustandsänderung — sodass die Plattform jederzeit den aktuellen App-Stand kennt.

send({
  type: 'matheforscher:state',
  state: { /* beliebiges JSON-Objekt, app-spezifisch */ },
  beschreibung: '7 Plättchen gelegt: 3 oben, 4 unten — Zerlegung 3+4 sichtbar'  // optional, ab v2.9
});

Plattform-Effekt: Der zuletzt gesendete State wird im Schüler-Store gehalten. Beim KI-Check wird er an die KI übergeben (max 4 KB JSON).

beschreibung (optional, empfohlen — semantische Selbstbeschreibung): Ein kurzer deutscher Satz, was der aktuelle Zustand bedeutet („Zerlegung 3+4 sichtbar", „Feld ist leer"). Die Plattform stellt ihn der KI vor dem Roh-JSON zur Verfügung — Tutor und Auswertung beziehen sich dann direkt auf das Material, ohne dein State-Format raten zu müssen. Das ist der wirksamste Einzel-Schritt, um deine App „KI-optimal" zu machen.

Wichtig:

  • State muss JSON-serialisierbar sein (keine Funktionen, keine DOM-Knoten, keine Klassen).
  • Halte den State so kompakt wie möglich, aber so vollständig wie nötig, damit die KI ihn verstehen kann.
  • Bei Bedarf zwischenstand komprimieren — z.B. ein Rechenfeld als 2D-Array [[1,1,0],[1,1,0]] statt verbose Cell-Objects.
  • Drosseln: State nur senden, wenn er sich tatsächlich geändert hat. Bei kontinuierlichen Änderungen (Drag) am Ende der Geste senden, nicht bei jedem Pixel.

matheforscher:discover (Plattform → App)

Wann empfangen: Bei der App-Registrierung (Lehrkraft-Bereich → Apps → „🔄 Konfiguration abrufen"). Die Plattform lädt deine App in einem versteckten Iframe und möchte dein supportedParams-Schema sehen — damit beim Auftrag-Bauen die KI sinnvolle Defaults pro Teilaufgabe vorschlagen kann.

{ type: 'matheforscher:discover', host: 'matheforscher', version: 1 }

Antwort: Sende ein normales matheforscher:ready mit payload.supportedParams zurück. Wenn deine App bereits beim Start matheforscher:ready sendet, ist discover redundant aber unschädlich — antworte einfach noch einmal mit demselben Schema.

Plattform-Effekt: Die Plattform speichert supportedParams auf dem App-Dokument. Bei der nächsten KI-Auftragsgenerierung steht das Schema dem Modell als Kontext zur Verfügung.

matheforscher:set-config (Plattform → App)

Wann empfangen: Beim Wechsel zwischen Teilaufgaben — wenn die Lehrkraft pro Aufgabe unterschiedliche Parameter konfiguriert hat. Anstelle eines Iframe-Reloads (mit State-Verlust) bittet die Plattform deine App, ihre Konfiguration live umzuschalten.

{
  type: 'matheforscher:set-config',
  payload: {
    config: { zr: 100, modus: 'darstellung' },
    reason: 'teilaufgabe-wechsel'  // oder 'auftrag-start'
  }
}

App-Pflicht: Innerhalb von 500 ms mit matheforscher:configured antworten (siehe nächster Abschnitt), sonst macht die Plattform einen Reload mit neuen URL-Parametern.

// Empfänger in deiner App:
window.addEventListener('message', (e) => {
  if (e.data?.type !== 'matheforscher:set-config') return;
  applyConfig(e.data.payload.config);  // deine App-Logik
  e.source.postMessage({
    type: 'matheforscher:configured',
    payload: { config: e.data.payload.config }
  }, e.origin);
});

Tipp: Wenn ein Wechsel der Config einen vollständigen Reset des App-Inhalts erfordert (z.B. anderer Zahlenraum), führe ihn selbst durch — der State-Verlust ist dann erwünscht und nicht schlimm.

matheforscher:configured (App → Plattform)

Wann senden: Als Antwort auf matheforscher:set-config, nachdem die neue Konfiguration in der App angewandt wurde.

e.source.postMessage({
  type: 'matheforscher:configured',
  payload: { config: { zr: 100, modus: 'darstellung' } }
}, e.origin);

Plattform-Effekt: Plattform stoppt den 500-ms-Timer und verzichtet auf den Reload. Der angegebene config-Snapshot wird im Schüler-Store gespeichert (sichtbar im KI-State-Check und Lehrkraft-Dashboard).

Fallback: Apps, die das Event nicht senden (z.B. ältere Apps ohne Live-Reconfigure-Support), bekommen automatisch einen Iframe-Reload mit neuen URL-Parametern — der bisherige Pfad funktioniert unverändert weiter.

matheforscher:highlight (Plattform → App)

Wann empfangen: Wenn die KI sprachbegleitend ein Element hervorheben möchte (z.B. „Schau mal auf die Summenanzeige"). Wird nur gesendet, wenn deine App im Manifest highlightTargets deklariert hat.

Format:

{
  type: 'matheforscher:highlight',
  payload: {
    targets: ['summe'],          // Pflicht (außer bei clear): 1..n deklarierte IDs
    style: 'pulse',              // optional: 'pulse'|'outline'|'arrow'|'spotlight'|'point'|<eigener String>
    label: 'Schau hier',         // optional: kurzer Text, den die App am Element zeigen darf
    durationMs: 4000,            // optional: automatisch nach N ms entfernen (fehlt/0 = bis zum nächsten Befehl)
    intensity: 'normal',         // optional: 'subtle'|'normal'|'strong'
    clearPrevious: true,         // optional: Wunsch, vorherige Hervorhebungen zuerst zu entfernen. Kein Plattform-Default — fehlt das Feld, entscheidet deine App (das Empfänger-Beispiel unten löscht ohnehin immer zuerst).
    reason: 'tutor'              // optional: Herkunft (App-Analytics, ignorierbar)
  }
}

Löschen aller Hervorhebungen: { type: 'matheforscher:highlight', payload: { clear: true } }

Regeln (robust & abwärtskompatibel):

  • Du musst nur targets verstehen — alle anderen Felder sind optionale Wünsche, die du honorieren oder ignorieren darfst.
  • Unbekannte IDs still ignorieren. style ist eine Konvention, kein Enum — mappe sie auf deine eigene Optik.
  • Apps ohne Highlight-Support ignorieren den unbekannten type — nichts geht kaputt.

Style-Empfehlungen:

Style Gedacht für
pulse sanftes Pulsieren (Default-Wahl für „schau hier")
outline Rahmen/Umrandung
spotlight Umgebung abdunkeln, Element hervortreten lassen
arrow Pfeil/Marker, der auf das Element zeigt
point dezenter Punkt/Indikator

Empfänger (DOM-Beispiel):

window.addEventListener('message', (e) => {
  if (e.data?.type !== 'matheforscher:highlight') return;
  const p = e.data.payload || {};
  document.querySelectorAll('.mf-highlight').forEach((el) => el.classList.remove('mf-highlight'));
  if (p.clear) return;
  const applied = [], ignored = [];
  for (const id of (p.targets || [])) {
    const el = document.getElementById(id);            // oder dein eigenes id→Element-Mapping
    if (el) { el.classList.add('mf-highlight'); applied.push(id); } else ignored.push(id);
  }
  if (p.durationMs) setTimeout(() => applied.forEach((id) => document.getElementById(id)?.classList.remove('mf-highlight')), p.durationMs);
  e.source?.postMessage({ type: 'matheforscher:highlighted', payload: { applied, ignored } }, e.origin || '*');
});

Damit das funktioniert, deklariere die hervorhebbaren Elemente im Manifest:

highlightTargets: [{ id: 'summe', label: 'die Summenanzeige oben' }]

matheforscher:perform (Plattform → App)

Wann empfangen: Wenn die KI eine Aktion am Material vorführen (mode: 'demo') oder echt ausführen (mode: 'execute') möchte — z.B. einen ersten Modell-Schritt zeigen. Wird nur gesendet, wenn deine App im Manifest performableActions deklariert hat.

Format:

{
  type: 'matheforscher:perform',
  payload: {
    mode: 'demo',                // 'demo' = vorführen OHNE echten Zustands-Change | 'execute' = echt anwenden
    steps: [                     // Pflicht: 1..n Schritte (Form wie das matheforscher:action-Event)
      { action: 'place', target: 'zelle', payload: { row: 0, col: 0 } },
      { action: 'place', target: 'zelle', payload: { row: 0, col: 1 } }
    ],
    stepDelayMs: 600,            // optional: Pause zwischen Schritten beim Abspielen
    label: 'So könntest du anfangen',  // optional: Begleittext, den die App zeigen darf
    reason: 'tutor'             // optional: Herkunft
  }
}

Semantik & Regeln:

  • mode: 'demo' — zeige die Schritte als Vorschau/Animation, OHNE den fachlichen Zustand zu ändern (kein state/complete als Folge). Das Kind macht es danach selbst.
  • mode: 'execute' — wende die Schritte echt an (du darfst danach wie bei Kind-Aktionen state/action senden).
  • Unbekannten mode gnädig behandeln (am besten als demo). Schritte mit unbekannter action/target überspringen, übrige ausführen.
  • execute ist NICHT idempotent — die Plattform sendet jede perform-Message genau einmal (keine Retries). demo ist gefahrlos wiederholbar.
  • Apps ohne Perform-Support ignorieren den unbekannten type.
  • Pro Schritt werden nur action, target und payload übermittelt — zusätzliche Felder an einem Schritt werden von der Plattform verworfen.

Empfänger (Beispiel):

window.addEventListener('message', async (e) => {
  if (e.data?.type !== 'matheforscher:perform') return;
  const p = e.data.payload || {};
  let applied = 0, skipped = 0;
  for (const step of (p.steps || [])) {
    const fn = appActionMap[step.action];              // dein action→Funktion-Mapping
    if (!fn) { skipped++; continue; }
    await fn(step.target, step.payload, { mode: p.mode || 'demo' });  // App entscheidet demo vs. execute
    applied++;
    if (p.stepDelayMs) await new Promise((r) => setTimeout(r, p.stepDelayMs));
  }
  e.source?.postMessage({ type: 'matheforscher:performed', payload: { mode: p.mode || 'demo', appliedSteps: applied, skippedSteps: skipped } }, e.origin || '*');
});

Deklariere die fern-aufrufbaren Aktionen im Manifest:

performableActions: [
  { action: 'place', target: 'zelle', label: 'eine Zelle füllen', payloadSchema: 'payload {row,col}' },
  { action: 'reset', label: 'das Feld leeren' }
]

matheforscher:highlighted (App → Plattform)

Optional. Bestätigt, welche Elemente hervorgehoben wurden. Nützlich für späteres Dashboard/KI-Feedback; die Plattform funktioniert auch ohne.

Format: { type: 'matheforscher:highlighted', payload: { applied: ['summe'], ignored: [] } }

matheforscher:performed (App → Plattform)

Optional. Bestätigt eine ausgeführte/vorgeführte Sequenz.

Format: { type: 'matheforscher:performed', payload: { mode: 'demo', appliedSteps: 2, skippedSteps: 0 } }


Schema-Beschreibung für den App-Pool

Damit die KI deine state- und action-Daten interpretieren kann, beschreibst du im App-Pool (Lehrkraft-Bereich → App registrieren → „Was sendet die App per postMessage?"), was deine App sendet. Die KI bekommt diese Prosa-Beschreibung mit jedem State-Check.

Empfohlene Struktur

## State-Format
state.{feldname1}: {Typ + Bedeutung}
state.{feldname2}: {Typ + Bedeutung}

## Actions
- action="{verb}" mit target="{ziel}" und payload={schema}: bedeutet …
- action="{verb}" …

## Erfolg / Lösung
Eine Aufgabe gilt als gelöst, wenn …

Beispiel: Rechenfeld

## State-Format
state.felder: 2D-Array, Form [zeilen][spalten].
  Werte: 0 = leer, 1 = blau gefüllt, 2 = rot gefüllt.
  Übliche Größen: 5×5, 10×10 (zwanzigerfeld), 10×10 (hunderterfeld).
state.gesamtAnzahl: Anzahl der gefüllten Felder (Zahl).
state.zerlegung: Wenn Aufgabentyp "Zerlegung", dann { links: 4, rechts: 6 }.

## Actions
- action="place" mit target="plättchen" und payload={row, col, color}:
  Kind setzt ein Plättchen in der genannten Zelle.
- action="remove" mit target="plättchen" und payload={row, col}:
  Kind entfernt ein Plättchen.
- action="reset": Alle Plättchen weg.
- action="check": Kind hat den "Prüfen"-Button gedrückt.

## Erfolg / Lösung
Bei Zerlegungs-Aufgaben: state.zerlegung.links + state.zerlegung.rechts == zielzahl.
Bei Anzahl-Aufgaben: state.gesamtAnzahl == zielzahl.
Es gibt keine globale Erfolgs-Bedingung — die hängt vom Auftrag ab.

Beispiel: Stellenwerttafel

## State-Format
state.bündel: { tausender: 0, hunderter: 2, zehner: 5, einer: 7 }.
state.zahlwert: errechnete Zahl, also tausender*1000 + hunderter*100 + ... = 257.
state.darstellung: 'plättchen' | 'striche' | 'zahlen' — wie das Kind gerade visualisiert.

## Actions
- action="add" mit target="zehner": Eine Zehner-Einheit hinzugefügt.
- action="remove" mit target="zehner": Eine Zehner-Einheit entfernt.
- action="bündeln" mit target="zehner": 10 Einer wurden zu 1 Zehner gebündelt.
- action="entbündeln" mit target="zehner": 1 Zehner wurde zu 10 Einern entbündelt.

## Erfolg / Lösung
Aufgabenabhängig — meist „state.zahlwert == zielzahl".

Tipps für die Schema-Beschreibung

  • Schreib in Prosa. Die KI versteht „2D-Array, 1 = blau" besser als JSON-Schema.
  • Gib Beispiele. Ein konkreter Beispiel-State hilft der KI mehr als abstrakte Definitionen.
  • Erkläre Fachbegriffe. Wenn die App „Bündeln" als spezielle Aktion kennt, beschreib's.
  • Bleib kurz. 200-400 Wörter sind ein guter Richtwert. Mehr lenkt eher ab.

Best Practices

1. Sende nur, wenn eingebettet

Spar dir das Geschick außerhalb der Plattform — keine zusätzlichen Console-Logs, keine Fremd-Origin-Aufrufe.

const isEmbedded = new URLSearchParams(location.search).has('matheforscher');
const send = (msg) => isEmbedded && window.parent.postMessage(msg, '*');

2. Drossel den State

Bei kontinuierlichen Änderungen (Drag, Slider) erst am Ende senden:

let stateTimer = null;
function queueStateUpdate() {
  if (stateTimer) clearTimeout(stateTimer);
  stateTimer = setTimeout(() => send({ type: 'matheforscher:state', state: getCurrentState() }), 250);
}

3. Action vs. Progress vs. Complete

Sender Plattform-UI Use-Case
action unsichtbar (im Hintergrund) Jede einzelne Handlung — Lernverlauf
progress Chip im Schüler-UI Bedeutsamer Zwischenschritt — Lehrkraft soll's sehen
complete Häkchen automatisch (informativ) Nur wenn die App selbst sicher weiß, dass die Aufgabe fertig ist

Beispiel-Sequenz im Rechenfeld bei „Zerlegung der 10":

action: place plättchen     (× viele, im Hintergrund)
state: { gesamtAnzahl: 7 }  (× viele, im Hintergrund)
action: place plättchen
state: { gesamtAnzahl: 10, zerlegung: { links: 7, rechts: 3 } }
progress: '10 = 7 + 3'      (Chip!) ← sichtbar für Lehrkraft + Kind

complete würde man hier NICHT senden, weil der Auftrag „Finde alle Zerlegungen" lautet und 7+3 nur eine ist.

Wann complete senden?

Sende complete nur, wenn deine App selbst mit Sicherheit weiß, dass die Aufgabe abgeschlossen ist — z.B. weil die Zielzahl exakt erreicht wurde oder weil alle Pflichtfelder korrekt ausgefüllt sind. In allen anderen Fällen reicht state + action — die Plattform entscheidet selbst, ob das Ziel der Lehrkraft erreicht ist, anhand der KI-gestützten Erfolgs-Erkennung über die vom der Lehrkraft definierte Erfolgs-Bedingung.

4. JSON-serialisierbar

State und payloads müssen JSON.stringify-fähig sein:

  • ✅ Numbers, strings, booleans, arrays, plain objects, null
  • ❌ Functions, Dates (statt dessen ISO-String), DOM-Nodes, Maps/Sets, undefined

5. Datenschutz: kein PII

Sende nichts, was identifizierbar ist. Insbesondere nichts vom Schüler-Pseudonym oder localStorage-Inhalten — die Plattform kennt das selbst und braucht es nicht von der App.

6. Höre auf das hello-Event statt nur URL zu prüfen

Wenn deine App ein Client-Side-Router-Bootstrap hat, der URL-Parameter wegräumt, ist ?matheforscher=1 schnell weg. Das matheforscher:hello-postMessage kommt nach dem App-Bootstrap und ist damit zuverlässig — siehe Detection-Snippet oben.

7. Niemals Parent-DOM direkt lesen

window.parent.location, window.top.document, window.parent.frameElement.style… etc. lösen in Cross-Origin-Iframes (Standard-Fall) SecurityError aus. Verwende ausschließlich window.postMessage für Kommunikation zur Plattform und window.self !== window.top für die reine Embedding-Erkennung.

8. Plattform → App: aktuelle Events

In der aktuellen Version sendet die Plattform diese Events an deine App: hello, discover, request-screenshot, set-config, highlight und perform. Alle sind optional zu behandeln — deine App ignoriert, was sie nicht unterstützt. Wenn du weitere Use-Cases brauchst, nimm Kontakt auf.


Komplettbeispiel: Mini-Rechenfeld

<!doctype html>
<html lang="de">
<head>
  <meta charset="utf-8">
  <title>Mini-Rechenfeld</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; }
    .feld { display: grid; grid-template-columns: repeat(10, 28px); gap: 2px; margin: 16px 0; }
    .zelle { width: 28px; height: 28px; border: 1px solid #ccc; cursor: pointer; }
    .zelle.gefüllt { background: #1f5a8c; }
    button { padding: 8px 14px; font: inherit; cursor: pointer; }
  </style>
</head>
<body>
  <h1>Mini-Rechenfeld</h1>
  <div class="feld" id="feld"></div>
  <p>Anzahl gefüllt: <b id="anzahl">0</b></p>
  <button id="reset">Zurücksetzen</button>
  <button id="fertig">Fertig</button>

<script>
const ROWS = 5, COLS = 10;
const isEmbedded = new URLSearchParams(location.search).has('matheforscher');
const send = (msg) => isEmbedded && window.parent.postMessage(msg, '*');

let felder = Array.from({ length: ROWS }, () => Array(COLS).fill(0));

function render() {
  const el = document.getElementById('feld');
  el.innerHTML = '';
  for (let r = 0; r < ROWS; r++) {
    for (let c = 0; c < COLS; c++) {
      const z = document.createElement('div');
      z.className = 'zelle' + (felder[r][c] ? ' gefüllt' : '');
      z.onclick = () => toggle(r, c);
      el.appendChild(z);
    }
  }
  document.getElementById('anzahl').textContent = anzahl();
}

function anzahl() { return felder.flat().filter(Boolean).length; }

function toggle(r, c) {
  const wasFilled = felder[r][c] === 1;
  felder[r][c] = wasFilled ? 0 : 1;
  render();
  send({
    type: 'matheforscher:action',
    action: wasFilled ? 'remove' : 'place',
    target: 'plättchen',
    payload: { row: r, col: c }
  });
  send({
    type: 'matheforscher:state',
    state: { felder, gesamtAnzahl: anzahl() }
  });
  // wenn z.B. genau 10 Plättchen — als Progress melden
  if (anzahl() === 10) {
    send({ type: 'matheforscher:progress', teilaufgabeHinweis: '10 Plättchen gelegt' });
  }
}

document.getElementById('reset').onclick = () => {
  felder = Array.from({ length: ROWS }, () => Array(COLS).fill(0));
  render();
  send({ type: 'matheforscher:action', action: 'reset' });
  send({ type: 'matheforscher:state', state: { felder, gesamtAnzahl: 0 } });
};

document.getElementById('fertig').onclick = () => {
  send({ type: 'matheforscher:complete', payload: { gesamtAnzahl: anzahl() } });
};

render();
</script>
</body>
</html>

Passende Schema-Beschreibung für den App-Pool:

## State-Format
state.felder: 2D-Array Form [5][10] (5 Zeilen, 10 Spalten).
  Werte: 0 = leer, 1 = blau gefüllt.
state.gesamtAnzahl: Anzahl gefüllter Zellen (Zahl 0…50).

## Actions
- action="place" mit target="plättchen" und payload={row, col}: Kind hat ein Plättchen gesetzt.
- action="remove" mit target="plättchen" und payload={row, col}: Kind hat ein Plättchen entfernt.
- action="reset": Alle Plättchen weg.

## Erfolg
Aufgabenabhängig — bei „Lege X Plättchen" gilt state.gesamtAnzahl == X.

Browser-Kompatibilität & Stolpersteine

Das Protokoll nutzt nur seit Jahren etablierte Standard-APIs und funktioniert in allen aktuellen Browsern:

Browser Mindest-Version Status
Chrome / Edge / Opera 90+ ✅ vollständig
Firefox 88+ ✅ vollständig
Safari (macOS) 14+ ✅ vollständig
Safari (iOS / iPadOS) 14+ ✅ vollständig
Samsung Internet 14+ ✅ vollständig

Konkret: window.postMessage, addEventListener('message', …), URLSearchParams, dynamic import(), canvas.toDataURL, navigator.clipboard.writeText sind in allen genannten Versionen verfügbar.

Damit's wirklich „von selbst" läuft

Wenn du den Auto-Init-Block eingebaut hast, brauchst du am Browser nichts weiter zu konfigurieren. Achte aber auf folgende typische Stolpersteine, die sonst zu stillen Ausfällen führen:

Stolperstein Symptom Lösung
Strenge CSP in deiner App (script-src 'self', connect-src 'self') import('https://cdn.jsdelivr.net/...') schlägt fehl, Screenshot-DOM-Fallback funktioniert nicht html2canvas-pro per npm install als lokale Dependency bündeln (siehe Auto-Init-Block)
X-Frame-Options: DENY oder restriktive frame-ancestors-CSP auf deinem Server Iframe lädt gar nicht, leere Fläche im Schüler-UI Auf deinem App-Server X-Frame-Options weglassen oder ALLOWALL setzen; CSP frame-ancestors * (oder explizit https://matheforscher-prod.web.app) erlauben
Tippfehler im type-String (z. B. mathforscher:state ohne e) Kein Eintrag im Lehrkraft-Dashboard, KI-Check liefert nichts Konsole mit Filter „matheforscher" prüfen; type muss exakt matheforscher:<name> lauten
Service Worker entfernt Query-Params App lädt, aber ?matheforscher=1 ist weg Hash-Signal #matheforscher=1 und matheforscher:hello-postMessage fangen das auf — nichts zu tun, solange dein Listener registriert ist
State nicht JSON-serialisierbar (Date, Map, Function, DOM-Node) KI-Check antwortet generisch, weil State leer ankommt Im State nur Plain-Objects/Primitives, Datumswerte als ISO-String
canvas.toDataURL() wirft SecurityError Bilder/Tiles auf dem Canvas kommen von fremder Origin ohne CORS Bilder mit crossorigin="anonymous" laden; Server muss Access-Control-Allow-Origin setzen
Privacy-Erweiterungen (Brave Shields strict, uBlock advanced) Iframe lädt teilweise nicht, postMessage kommt nicht an Tritt im Schul-Setup praktisch nicht auf — beim eigenen Test ggf. Standard-Profil nutzen

Warum „von selbst funktionieren" hier wirklich gilt

Das Protokoll ist bewusst so entworfen, dass deine App kein Plattform-Wissen braucht:

  1. Du registrierst nur deine App-URL (und optional Default-Parameter) in der Plattform.
  2. Es gibt keine Authentifizierung, keine Tokens, keinen Handshake-State, der zwischen Plattform und App synchron sein müsste.
  3. Die Plattform validiert Origins automatisch und ignoriert ungültige Messages — wer falsch sendet, kann die Plattform also nicht in einen kaputten Zustand bringen.
  4. Alle Plattform→App-Events (hello, request-screenshot) sind idempotent und können verloren gehen oder mehrfach kommen, ohne dass die App Buch führen muss.
  5. Alle App→Plattform-Events sind additiv: nichts musst du senden, alles kannst du senden — die Plattform fügt nur dazu, was sie versteht.

Testen + Debugging

Lokal mit der Live-Plattform testen

Während der Entwicklung kannst du deine App lokal hosten (z.B. localhost:5500) und in der Plattform als App registrieren mit URL http://localhost:5500/...?matheforscher=1. Wichtig: dein lokaler Server muss CORS erlauben oder die Plattform muss CORS-tolerant sein (ist sie für eingebettete iframes — du brauchst nichts zu tun, solange dein Server keine restriktiven X-Frame-Options setzt).

Browser-Konsole

In der Schüler-Ansicht öffne DevTools, Tab „Console". Filtere auf matheforscher. Du siehst alle Events, die die Plattform empfängt.

Du kannst manuell senden, um zu testen:

window.parent.postMessage({ type: 'matheforscher:state', state: { test: 1 } }, '*');

State-Check probieren

Nach ein paar Actions/States: drück den „🤖 Wie weit bin ich?"-Button im Schüler-UI. Die KI bekommt deinen aktuellen State + Schema-Beschreibung + Aufgabentext und antwortet. Wenn die Antwort generisch ist, ist meist die Schema-Beschreibung zu mager.


Datenschutz

  • Keine personenbezogenen Daten in Events. Pseudonyme verwaltet die Plattform selbst.
  • Keine externen Server-Calls während eingebettet, wenn vermeidbar — die Plattform protokolliert ihrerseits nicht alle Apps.
  • Audio/Video in der App selbst (Mikrofon, Kamera): das ist deine Verantwortung. Hol dir Permission, wenn nötig, und nutze nichts dauerhaft.
  • Drittanbieter-Tracking (Google Analytics, Hotjar, etc.) sollte in der eingebetteten Variante deaktiviert sein. Nutze den matheforscher=1-Parameter dafür.

Protokoll v3.0 — Integrierte Hosts (Profil-Kontext & Reporting)

Ab v3.0 kann ein Host (z. B. die urff.app-Super-App oder die Matheforscher-Plattform) deiner App Kontext übergeben (aktives Kind-Profil) und strukturierte Ergebnisse entgegennehmen. Alles bleibt additiv und optional.

Erweitertes hello (Host → App)

{
  type: 'matheforscher:hello',
  host: 'urffapp',                    // oder 'matheforscher'
  version: 3,
  capabilities: ['context', 'ergebnis', 'report'],   // was DIESER Host verwertet
  context: {
    profil: { id: 'p_abc', name: 'Lina', emoji: '🦊', farbe: '#e8734a' },  // null = Gast/anonym
    locale: 'de-DE'
  }
}
  • hello bleibt idempotent (bis zu 6× alle 500 ms, bis ready eintrifft).
  • profil.id ist der stabile Schlüssel; name/emoji/farbe sind Anzeige-Attribute, die sich bei gleicher id ändern dürfen (Profil umbenannt → App zieht beim nächsten hello nach).
  • capabilities nennt die Events, die der Host versteht — sende z. B. ergebnis nur, wenn es dort auch verwertet wird.
  • Hosts ohne v3 senden die Felder nicht → deine App verhält sich wie bisher.

Profil-Adoption

Wenn context.profil != null und deine App Adoption unterstützt (Manifest profilAdoption: true):

  1. Internen Benutzer mit externId == profil.id suchen — falls keiner existiert, automatisch anlegen (Name/Emoji/Farbe aus dem Profil).
  2. Diesen Benutzer auswählen und die komplette Benutzerverwaltung ausblenden (Auswahl, Anlegen, Wechseln, Löschen).
  3. Optional bestätigen: matheforscher:context-applied { payload: { profil: { id }, adoption: 'uebernommen' | 'neu-angelegt' } }

Bei profil: null (Gast) verhält sich die App wie bisher. Best Practice: Im eingebetteten Modus (window.self !== window.top) bis max. 1 s auf das erste hello warten, bevor du deine eigene Benutzerauswahl zeigst — so blitzt der Benutzer-Picker nicht auf (das erste hello kommt praktisch sofort nach load).

set-context / context-applied (Laufzeit-Profilwechsel)

Symmetrisch zu set-config/configured: Host sendet { type: 'matheforscher:set-context', payload: { context, reason } }; deine App wendet den Kontext an und antwortet binnen 500 ms mit matheforscher:context-applied, sonst macht der Host einen iframe-Reload.

matheforscher:ergebnis (App → Host)

Ein bewertetes Item — statistisch auswertbar ohne KI. Sende es immer dann, wenn das Kind eine Aufgabe abgeschlossen hat:

send({
  type: 'matheforscher:ergebnis',
  payload: {
    aufgabe: '7 + 5',                 // Pflicht: menschenlesbare Aufgabenstellung
    korrekt: true,                    // Pflicht: true | false | null (nicht bewertbar)
    typ: 'addition-zr20',             // optional: app-interner Aufgabentyp-Code
    antwort: '12', erwartet: '12',    // optional
    dauerMs: 4200,                    // optional: Bearbeitungszeit
    versuche: 1, hilfen: 0,           // optional
    fehlertyp: 'zehnerübergang',      // optional: app-diagnostizierter Fehlertyp
    didaktik: { zahlenraum: 'ZR20', inhaltsbereich: 'Arithmetik' },  // optional, Vokabular wie Manifest-didaktik
    beschreibung: 'Kind rechnet 7+5=12 im ersten Versuch'            // optional, KI-Satz wie bei state/action
  }
});

Abgrenzung: action = jede Handlung (Rohverlauf) · progress = sichtbarer Zwischenschritt · ergebnis = bewertetes Item (Quote, Zeiten, Fehlertypen berechenbar). Payload-Limit: 8 KB.

request-report / report — aggregierte Diagnostik

// Host → App (z. B. beim Sitzungsende):
{ type: 'matheforscher:request-report', payload: { scope: 'sitzung' } }   // 'sitzung' | 'gesamt'

// App → Host (als Antwort ODER unaufgefordert, z. B. nach abgeschlossenem Durchgang):
{ type: 'matheforscher:report', payload: {
    scope: 'sitzung',
    zusammenfassung: '24 Aufgaben im ZR20, 19 richtig. Schwierigkeiten beim Zehnerübergang.',
    kennzahlen: { bearbeitet: 24, korrekt: 19, dauerSec: 540 },   // flaches Key-Value für Statistik
    diagnostik: { /* app-spezifisch, Schema im Manifest (reportSchema) beschrieben */ },
    empfehlung: 'Weiter mit Zehnerübergang im ZR20 üben.'          // optional
} }

Antworte auf request-report zügig (Hosts warten typischerweise unter 1 s). Mehrere Reports pro Sitzung sind erlaubt; der letzte pro Scope gilt. Payload-Limit: 64 KB.

Manifest-Erweiterungen (v3)

Feld Bedeutung
profilAdoption boolean — App kann Profile übernehmen und blendet dann ihre Benutzerverwaltung aus
reporting { ergebnis: true, reportScopes: ['sitzung','gesamt'], reportSchema: 'Prosa…' } — was die App liefert; reportSchema beschreibt das diagnostik-Objekt
protocolLevel: 4 Neues Conformance-Level „integriert" (context + ergebnis/report) über Level 3

Datenschutz (v3)

  • Kontextdaten (Profil) dienen nur der lokalen Zuordnung/Anzeige und dürfen von Apps niemals an eigene Server oder Dritte übertragen werden.
  • Hosts senden nur Anzeigenamen/Pseudonyme, keine Klarnamen-Pflicht.
  • ergebnis/report enthalten keine Personendaten — die Zuordnung zum Kind macht der Host über die Sitzung.

Versions-History

Version Stand Neuerungen
1.0 Mai 2026 Initial: progress, complete, error
2.0 Mai 2026 + action, state, App-Pool stateSchema-Feld, KI-State-Check
2.1 Mai 2026 erfolgsBedingung in Aufträgen, Plattform-seitige Erfolgs-Erkennung; complete als informativ/optional markiert
2.2 Mai 2026 request-screenshot / screenshot Events für automatischen App-Screenshot
2.3 Mai 2026 + ready-Event mit config/supportedParams, Konfigurations-Doku, Screenshot-Beispiele
2.4 Mai 2026 + hello-Event (Plattform → App, mehrfaches Pingen), zusätzliches #matheforscher=1-Hash-Signal, Anti-Pattern-Hinweis zu Cross-Origin-Parent-Zugriff, robusterer URL-Bau
2.5 Mai 2026 + Vollständiger Auto-Init-Block, iframe-Berechtigungen explizit dokumentiert, Browser-Kompatibilität (Chrome/Firefox/Safari/iOS) + häufige Stolpersteine (CSP, X-Frame-Options, Service Worker, canvas-CORS), html2canvas-Bundling als CSP-Fallback empfohlen
2.6 Mai 2026 + discover-Event (Plattform fragt aktiv nach supportedParams bei der App-Registrierung), + set-config/configured-Eventpaar für Live-Re-Konfiguration der App bei Teilaufgaben-Wechsel ohne Iframe-Reload (Fallback auf URL-Reload, wenn App nicht antwortet)
2.7 Mai 2026 + manifest-Payload im ready-Event mit Selbstbeschreibung der App (Name, Beschreibung, kindActions, Kategorien, stateSchema, defaultParams, iconUrl, infoUrl, version). Plattform fragt das Manifest beim App-Anlegen automatisch ab (1.5 s Debounce auf der URL-Eingabe) und bietet auf Knopfdruck Updates mit Hash-basierter Diff-Anzeige.
2.8 Mai 2026 + highlight/perform (Plattform → App): KI kann deklarierte Elemente hervorheben und Aktionen vorführen/ausführen (mode: 'demo' oder 'execute', Mehrschritt-Sequenz); Manifest-Felder highlightTargets/performableActions; optionale Acks highlighted/performed.
2.9 Juli 2026 + Semantische Selbstbeschreibung: optionales Feld beschreibung auf state- und action-Events — die App erklärt in einem deutschen Satz, was Zustand/Handlung bedeuten; die Plattform-KI (Tutor, Auswertung, State-Check) nutzt Beschreibungen bevorzugt statt Roh-JSON. + Manifest-Felder didaktik (Klassenstufen, Zahlenräume, EIS-Darstellungsebenen, Inhaltsbereiche, Kompetenzen), stateSchemaV2 (JSON-Schema + Prosa + Beispiel-Paare) und protocolLevel (0–3). + Offizielles Mini-SDK /sdk/forscher-protokoll.js (siehe docs/forscher-sdk.md).
3.0 Juli 2026 + Integrierte Hosts: hello mit host/version/capabilities/context (aktives Kind-Profil); Profil-Adoption mit ausgeblendeter Benutzerverwaltung (profilAdoption im Manifest, context-applied-Ack, set-context für Laufzeitwechsel); ergebnis-Event (bewertete Items fürs Lernprozesslog); request-report/report (aggregierte Diagnostik); Manifest-Felder reporting, protocolLevel 4; Datenschutz-Regeln für Kontextdaten. Erster v3-Host: urff.app.

Kontakt

Plattform-Maintainer: Christian Urff · matheforscher@urff.app · https://matheforscher-prod.web.app

Source: https://github.com/... (sobald öffentlich)

Lizenz dieser Doku: CC-BY 4.0 — gerne weitergeben und übersetzen.

mathe.digital · postMessage-Protokoll · Diese Seite wird statisch aus docs/postmessage-protokoll.md generiert. Fragen? matheforscher@urff.app

Diese Plattform speichert deine Bearbeitung lokal im Browser. Wenn eine Lehrkraft eine Sammel-Session startet, werden Pseudonym, Lösungsstand, Notizen und ggf. Sprachmemos/Screenshots auf einem deutschen Server (Firebase, europe-west3) gespeichert. Mit dem Klick auf „Verstanden" akzeptierst du das.