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
- Worum geht's?
- Quickstart (5 Zeilen)
- Vollständiger Auto-Init (alles in einem)
- Plattform-Erkennung
- iframe-Berechtigungen
- Konfiguration via Query-Parameter
- App-Manifest beim ready-Event
- Event-Typen
- Schema-Beschreibung für den App-Pool
- Best Practices
- Komplettbeispiel: Mini-Rechenfeld
- Browser-Kompatibilität & Stolpersteine
- Testen + Debugging
- Datenschutz
- Protokoll v3.2 — KI-Kanal (zentrale KI des Hosts)
- 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 |
Arbeitsmittel meldet „Ziel erreicht" — zählt als Bestätigung, wenn das Kind „Fertig – schau nach!" drückt (hakt nicht selbst ab, ab Doku 3.3) |
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], label: 'Zahlenraum' },
modus: {
type: 'string', default: 'frei', values: ['frei', 'zerlegung', 'darstellung'],
label: 'Übungsmodus',
description: 'frei = offenes Legen, zerlegung = Zahlzerlegungen finden, darstellung = Darstellungswechsel üben'
}
};
// 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). |
ki |
object | v3.2 — App nutzt den KI-Kanal des Hosts: { beschreibung: 'Tutor-Hinweise am Zahlenstrahl', faehigkeiten: ['text'] }. beschreibung erscheint in der Lehrkraft-Ansicht des Hosts, faehigkeiten nennt, was die App braucht (text, json, stream, vision). Details im Abschnitt Protokoll v3.2. |
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:readymitsenden oder nur als Antwort auf einenmatheforscher:discover-Ping liefern — die Plattform akzeptiert beides. - Bestehende Apps, die nur
supportedParamsohnemanifestschicken, 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], label: 'Zahlenraum' },
anzahl: { type: 'number', default: 12, min: 1, max: 30, label: 'Plättchen-Anzahl' },
farbe: { type: 'string', default: 'blau', values: ['blau', 'rot', 'grün'], label: 'Plättchen-Farbe' },
zeigeHilfe: { type: 'boolean', default: false, label: 'Strukturhilfe',
description: 'Blendet die Fünfer-Gliederung ein — nimmt die Zählstruktur vorweg' },
modus: { type: 'string', default: 'frei', values: ['frei', 'zerlegung', 'darstellung'], label: 'Übungsmodus',
description: 'frei = offenes Legen, zerlegung = Zahlzerlegungen finden, darstellung = Darstellungswechsel üben' }
}
}
});
Schema-Felder pro Parameter:
| Feld | Pflicht | Bedeutung |
|---|---|---|
type |
ja | 'number' | 'string' | 'boolean' |
default |
empfohlen | Wert, mit dem die App ohne Parameter startet |
values |
optional | erlaubte Werte (geschlossene Liste) |
min / max |
optional | Grenzen für number-Parameter ohne values-Liste |
label |
empfohlen | menschenlesbarer Kurzname (erscheint im Konfigurations-UI der Lehrkraft) |
description |
empfohlen | was der Parameter fachlich/didaktisch bewirkt — ein Satz genügt |
Warum label/description wichtig sind: Die Plattform konfiguriert deine App auch KI-gestützt — beim Erstellen eines Forscherauftrags schlägt die KI passende Parameter-Werte vor (z. B. Zahlenraum hochziehen, Strukturhilfe abschalten, Progression über Teilaufgaben). Die KI sieht dabei genau dieses Schema. Ein Key wie modus allein sagt ihr nichts — erst description macht die didaktische Wirkung verständlich. Je besser deine Parameter beschrieben sind, desto passgenauer wird deine App konfiguriert.
Plattform-Effekt:
- Die Plattform speichert
configundsupportedParamsim 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).
- Die KI-gestützte Auftrag-Erstellung nutzt das Schema (inkl.
label/description/min/max), um deine App passgenau zu konfigurieren.
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 bzw. das Ziel eines Material-Schritts — 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 Vorgaben der Lehrkraft.
send({
type: 'matheforscher:complete',
payload: { /* optional, z.B. Endstand */ }
});
Plattform-Effekt (seit Doku 3.3): complete hakt die Teilaufgabe nicht mehr selbst ab. Bei Schritten „Am Material zeigen" drückt das Kind „Fertig – schau nach!"; ist seit Beginn des Schritts ein complete eingetroffen, gilt der Schritt als vom Arbeitsmittel bestätigt:
- Hat die Lehrkraft keinen eigenen Zielsatz („Am Arbeitsmittel fertig heißt …") hinterlegt, ist der Schritt damit sofort bestanden.
- Mit eigenem Zielsatz prüft die KI zusätzlich den
state— der Zielsatz darf mehr verlangen als dein App-Ziel (z.B. „243 mit mehr als 10 Zehnern").
Boolesches state.erreicht hat Vorrang: Meldet dein aktueller state ein Feld erreicht: true | false, zählt nur dieses — ein älteres complete wird ignoriert, denn das Kind kann das Ziel nach dem complete wieder zerstört haben. Empfehlung: Apps mit selbst geprüftem Ziel senden erreicht in jedem state.
send({ type: 'matheforscher:state', state: { zahl: 243, zehner: 14, erreicht: true }, beschreibung: '243 gelegt, Ziel erreicht' });
Hinweis zur plattformseitigen Erfolgs-Erkennung: Lehrkräfte können pro Auftrag eine textuelle Erfolgs-Bedingung und pro Material-Schritt einen Zielsatz definieren (z.B. „Alle elf Zerlegungen der 10 sind dargestellt"). Die KI prüft anhand des aktuellen state und der Aktionen, ob diese erfüllt sind — unabhängig davon, ob die App ein complete-Event gesendet hat.
Ziel-Parameter (Konvention)
Kann dein Arbeitsmittel ein Ziel selbst prüfen, nimm den Zielwert als URL-Parameter entgegen und deklariere ihn in supportedParams unter einem dieser Namen:
| Name | Typische Verwendung |
|---|---|
ziel |
Zielzahl oder Zielzustand (z.B. Stellenwerte: ziel=243) |
zielzahl |
Zielzahl, wenn ziel schon anders belegt ist |
zielzeit |
Zielzeit HH:MM (z.B. Klassenzimmeruhr: zielzeit=09:10) |
target |
englische Variante von ziel |
goal |
englische Variante von ziel |
karte |
Forscherkarte mit eingebautem Ziel (z.B. Geobrett: karte=…) |
Ist einer dieser Parameter in supportedParams deklariert und für den Schritt gesetzt, zeigt die Plattform der Lehrkraft „Arbeitsmittel prüft selbst" und verlässt sich auf complete bzw. state.erreicht. Melde das Erreichen des Ziels dann zuverlässig (am besten über erreicht im state).
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 |
save |
etwas in eine Sammlung übernehmen (ab Doku 3.3, target: 'archiv') |
Lösung ins Archiv, Figur sammeln |
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.
Baustein „Sammlung" (ab Doku 3.3)
Apps mit Sammel- oder Archivfunktion (das Kind legt gefundene Lösungen in eine Liste) melden die ganze Sammlung als Liste im state — nicht nur den zuletzt gespeicherten Eintrag. Erst damit werden Aufgaben wie „Sammle alle Zerlegungen der 10" oder „Finde drei verschiedene Figuren mit Fläche 4" am Material prüfbar.
- State: ein Array mit einem Objekt pro Eintrag; pro Eintrag die prüfrelevanten Eigenschaften, nicht nur ein Bild oder eine ID.
- Vorbild Teiler:
state.archiv: [{ zahl: 12, teiler: [1, 2, 3, 4, 6, 12] }, …] - Geobrett:
state.archiv: [{ figur: 'Dreieck', flaeche: 2, kongruenzSignatur: '…' }, …]— die Signatur macht „anders als" bzw. „keine doppelt" prüfbar.
- Vorbild Teiler:
- Aktionen:
action: 'save'beim Hinzufügen,action: 'remove'beim Entfernen, jeweils mittarget: 'archiv'und dem Eintrag impayload:
send({ type: 'matheforscher:action', action: 'save', target: 'archiv', payload: { zahl: 12, teiler: [1, 2, 3, 4, 6, 12] }, beschreibung: 'Teiler von 12 gespeichert' });
send({ type: 'matheforscher:action', action: 'remove', target: 'archiv', payload: { zahl: 12 }, beschreibung: 'Teiler von 12 aus der Sammlung entfernt' });
send({ type: 'matheforscher:state', state: { archiv: [ /* alle Einträge */ ] }, beschreibung: '3 Zahlen gesammelt: 12, 18, 20' });
- Kompakt bleiben: Die Plattform reicht höchstens ~4 KB State-JSON an die KI weiter. Lieber knappe Eigenschaften je Eintrag als Rohdaten.
beschreibungnennen, was in der Sammlung liegt („3 Zahlen gesammelt: 12, 18, 20") — die KI stützt sich zuerst darauf.
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
targetsverstehen — alle anderen Felder sind optionale Wünsche, die du honorieren oder ignorieren darfst. - Unbekannte IDs still ignorieren.
styleist 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 (keinstate/completeals Folge). Das Kind macht es danach selbst.mode: 'execute'— wende die Schritte echt an (du darfst danach wie bei Kind-Aktionenstate/actionsenden).- Unbekannten
modegnädig behandeln (am besten alsdemo). Schritte mit unbekannteraction/targetüberspringen, übrige ausführen. executeist NICHT idempotent — die Plattform sendet jedeperform-Message genau einmal (keine Retries).demoist gefahrlos wiederholbar.- Apps ohne Perform-Support ignorieren den unbekannten
type. - Pro Schritt werden nur
action,targetundpayloadü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:
- Du registrierst nur deine App-URL (und optional Default-Parameter) in der Plattform.
- Es gibt keine Authentifizierung, keine Tokens, keinen Handshake-State, der zwischen Plattform und App synchron sein müsste.
- Die Plattform validiert Origins automatisch und ignoriert ungültige Messages — wer falsch sendet, kann die Plattform also nicht in einen kaputten Zustand bringen.
- 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. - 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'
}
}
hellobleibt idempotent (bis zu 6× alle 500 ms, bisreadyeintrifft).profil.idist der stabile Schlüssel;name/emoji/farbesind Anzeige-Attribute, die sich bei gleicher id ändern dürfen (Profil umbenannt → App zieht beim nächstenhellonach).capabilitiesnennt die Events, die der Host versteht — sende z. B.ergebnisnur, 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):
- Internen Benutzer mit
externId == profil.idsuchen — falls keiner existiert, automatisch anlegen (Name/Emoji/Farbe aus dem Profil). - Diesen Benutzer auswählen und die komplette Benutzerverwaltung ausblenden (Auswahl, Anlegen, Wechseln, Löschen).
- 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/reportenthalten keine Personendaten — die Zuordnung zum Kind macht der Host über die Sitzung.
Protokoll v3.2 — KI-Kanal (zentrale KI des Hosts)
Ab v3.2 kann ein Host seinen eingebetteten Apps eine zentrale KI anbieten. Deine App
schickt eine anbieterneutrale Chat-Anfrage (messages im OpenAI-Stil) an den Host; der
Host wählt Anbieter und Modell (eigener Schlüssel der Lehrkraft, KI auf dem Gerät oder ein
zentraler Zugang), führt die Anfrage aus und liefert die Antwort zurück. Die Prompts bleiben
in deiner App, der API-Schlüssel bleibt im Host. Alles ist additiv und optional.
Stand der Hosts: urff.app ist der erste Host mit KI-Kanal. mathe.digital selbst sendet
aktuell kein 'ki' in capabilities — dort verhalten sich Apps wie bisher (eigener
Zugang).
Erweitertes hello (Host → App)
{
type: 'matheforscher:hello', host: 'urffapp', version: 3,
capabilities: ['context', 'ergebnis', 'report', 'ki'], // 'ki' = Host bietet den Kanal an
context: { /* wie v3.0 */ },
ki: {
status: 'bereit', // 'bereit' | 'deaktiviert' | 'eigener-zugang'
faehigkeiten: ['text', 'json', 'stream', 'vision'], // nur bei 'bereit'
anbieter: 'byok' // nur bei 'bereit', informativ: 'byok' | 'zentral' | 'lokal'
}
}
Normatives Verhalten der App:
| Situation | Verhalten |
|---|---|
standalone / kein 'ki' in capabilities / kein ki-Feld |
eigener Zugang wie bisher |
status: 'eigener-zugang' |
eigener Zugang wie bisher (Host hat nichts konfiguriert) |
status: 'bereit' |
alle Chat-Anfragen über ki-request; eigene Schlüssel-/Anbieter-UI ausblenden |
status: 'deaktiviert' |
KI-Funktionen ausblenden oder deaktivieren; kein Rückfall auf den eigenen Zugang |
Fähigkeiten (faehigkeiten):
| Fähigkeit | Bedeutung |
|---|---|
text |
immer vorhanden — Textantwort in payload.text |
json |
Host garantiert bei antwortFormat: 'json' ein JSON-Objekt in payload.json |
stream |
Host liefert ki-chunk-Nachrichten vor der ki-response |
vision |
Bilder in content erlaubt |
Prüfe eine Fähigkeit, bevor du sie anforderst; fehlt sie, degradiere (z. B. Text statt JSON parsen, Bild weglassen).
matheforscher:ki-request (App → Host)
send({
type: 'matheforscher:ki-request',
id: 'k-17', // von der App gewählt, eindeutig pro Sitzung
payload: {
messages: [ // Pflicht, 1–64 Einträge
{ role: 'system', content: 'Du bist ein Mathe-Tutor für Klasse 2. Antworte in einem Satz.' },
{ role: 'user', content: [
{ type: 'text', text: 'Was zeigt das Bild?' },
{ type: 'image', dataUrl: 'data:image/png;base64,…' } // nur mit Fähigkeit 'vision'
] }
],
zweck: 'tutor-hinweis', // optional, ≤ 40 Zeichen: Statistik/Abrechnung
modell: 'schnell', // optional: 'schnell' | 'stark' (Klasse, kein Modellname); Default 'schnell'
temperature: 0.4, // optional, 0–2
maxTokens: 512, // optional, 1–8192
antwortFormat: 'text', // optional: 'text' | 'json'; Default 'text'
stream: false, // optional: true → ki-chunk vor ki-response
timeoutMs: 30000 // optional; Host klemmt auf 5 000–120 000; Default 60 000
}
});
role ist 'system', 'user' oder 'assistant'; content ist ein String oder ein Array
aus { type: 'text', text } und { type: 'image', dataUrl }. Alle Antworten des Hosts
tragen deine id.
Antworten: ki-chunk / ki-response / ki-error (Host → App)
// nur bei stream: true — beliebig viele, in Reihenfolge
{ type: 'matheforscher:ki-chunk', id: 'k-17', payload: { delta: 'Teil der Antwort' } }
// Erfolg
{ type: 'matheforscher:ki-response', id: 'k-17', payload: {
text: 'vollständige Antwort',
json: { /* … */ }, // nur bei antwortFormat 'json'
anbieter: 'byok', // 'byok' | 'zentral' | 'lokal'
modell: 'google/gemini-3.5-flash-lite', // optional
usage: { eingabe: 812, ausgabe: 97 } // optional, Tokens
} }
// Fehler
{ type: 'matheforscher:ki-error', id: 'k-17', payload: { code: 'limit', message: 'Warteschlange voll' } }
Pro id kommt genau eine Abschluss-Nachricht (ki-response oder ki-error).
Fehlercodes:
| Code | Bedeutung | Empfohlene Reaktion der App |
|---|---|---|
deaktiviert |
Lehrkraft hat KI im Host ausgeschaltet | KI-Funktion ausblenden; kein Rückfall auf eigenen Zugang |
eigener-zugang |
Host hat keine KI konfiguriert | eigenen Zugang wie bisher nutzen |
nicht-verfuegbar |
Anbieter momentan nicht nutzbar (z. B. lokales Modell wird geladen) | später erneut versuchen, Hinweis zeigen |
offline |
keine Netzverbindung | eigene Offline-Logik (nicht der eigene Online-Schlüssel) |
ungueltig |
Payload verletzt das Schema; message nennt das Feld |
Programmierfehler in der App beheben |
zu-gross |
Payload über dem Größenlimit | Verlauf kürzen, Bild verkleinern |
limit |
Rate-Limit erreicht oder Warteschlange voll | kurz warten, dann erneut |
timeout |
Host-Timeout (timeoutMs) überschritten |
erneut versuchen oder Offline-Logik |
abgebrochen |
durch ki-cancel oder Verlassen der App abgebrochen |
nichts anzeigen |
anbieter |
HTTP-/Modellfehler beim Anbieter; message enthält den Status |
Offline-Logik, Fehler dezent melden |
matheforscher:ki-cancel (App → Host)
send({ type: 'matheforscher:ki-cancel', id: 'k-17' });
Der Host bricht die Anfrage ab und antwortet mit ki-error { code: 'abgebrochen' }.
matheforscher:ki-status (Host → App, optional)
{ type: 'matheforscher:ki-status', payload: { status: 'deaktiviert' } } // payload = ki-Objekt wie im hello
Für Hosts, deren KI-Zustand sich zur Laufzeit ändert; behandle es wie ein neues hello.ki.
urff.app sendet es nicht (Apps werden beim Verlassen des Lehrkraft-Bereichs ohnehin neu
geladen); definiert für mathe.digital und künftige Hosts.
Manifest-Erweiterung (v3.2)
| Feld | Bedeutung |
|---|---|
ki |
{ beschreibung: 'Tutor-Hinweise am Zahlenstrahl', faehigkeiten: ['text'] } — App nutzt KI; beschreibung zeigt die Lehrkraft-Ansicht des Hosts, faehigkeiten nennt, was die App braucht |
Grenzen & Datenschutz (v3.2)
- Host-Limits: Payload ≤ 256 KB (Text) bzw. ≤ 4 MB mit Bildern; pro App-Sitzung max. 2
parallele Anfragen (weitere warten, ab 8 wartenden →
limit); 30 Anfragen/Minute. - Der Host leitet
messagesunverändert weiter und ergänzt keine Profil- oder Gerätedaten. - Apps dürfen keine Profil-/Klarnamen in Prompts schreiben (v3-Regel gilt fort).
- Der Host protokolliert nur Metadaten (App, Zweck, Anbieter, Modell, Tokens, Dauer, Erfolg), niemals Prompt- oder Antwortinhalte.
- Der API-Schlüssel verlässt den Host nie in Richtung App.
Minimalbeispiel: KI-Anfrage aus der App ohne SDK
Promise-Wrapper mit id-Korrelation und Timeout — Antworten werden nur vom Parent-Fenster
akzeptiert (e.source === window.parent):
let kiInfo = null; // ki-Objekt aus hello bzw. ki-status
const offen = new Map(); // id → { resolve, reject, timer, onChunk }
let zaehler = 0;
window.addEventListener('message', (e) => {
if (e.source !== window.parent) return;
const d = e.data || {};
if (d.type === 'matheforscher:hello') {
const hatKi = Array.isArray(d.capabilities) && d.capabilities.includes('ki');
kiInfo = hatKi && d.ki ? d.ki : { status: 'eigener-zugang' };
return;
}
if (d.type === 'matheforscher:ki-status') { kiInfo = d.payload || kiInfo; return; }
const o = d.id && offen.get(d.id);
if (!o) return;
if (d.type === 'matheforscher:ki-chunk') { o.onChunk?.(d.payload?.delta || ''); return; }
if (d.type === 'matheforscher:ki-response' || d.type === 'matheforscher:ki-error') {
clearTimeout(o.timer); offen.delete(d.id);
if (d.type === 'matheforscher:ki-response') return o.resolve(d.payload);
const err = new Error(d.payload?.message || d.payload?.code);
err.code = d.payload?.code;
o.reject(err);
}
});
const kiStatus = () => kiInfo?.status || 'eigener-zugang';
function kiAnfrage(payload, onChunk) {
return new Promise((resolve, reject) => {
if (kiStatus() !== 'bereit') {
return reject(Object.assign(new Error('KI-Kanal nicht bereit'), { code: kiStatus() }));
}
const id = 'k' + (++zaehler);
const frist = (payload.timeoutMs || 60000) + 5000; // etwas länger als der Host
const timer = setTimeout(() => {
offen.delete(id);
window.parent.postMessage({ type: 'matheforscher:ki-cancel', id }, '*');
reject(Object.assign(new Error('Zeitüberschreitung'), { code: 'timeout' }));
}, frist);
offen.set(id, { resolve, reject, timer, onChunk });
window.parent.postMessage({ type: 'matheforscher:ki-request', id, payload }, '*');
});
}
// Verwendung:
kiAnfrage({
messages: [{ role: 'user', content: 'Erkläre 7 + 5 in einem Satz für ein Kind in Klasse 2.' }],
zweck: 'tutor-hinweis', maxTokens: 120
}).then((antwort) => zeigeHinweis(antwort.text))
.catch((err) => {
if (err.code === 'deaktiviert') verbergeKiButton(); // kein Rückfall
else zeigeHinweis(offlineHinweis()); // eigene Fallback-Logik
});
Mit SDK (/sdk/forscher-protokoll.js): app.kiStatus(), app.kiFaehigkeiten(),
app.ki(payload, { onChunk }) (Promise), app.kiAbbrechen(id) und die Option
onKiStatus(ki) — siehe SDK-Doku, Abschnitt „KI-Kanal (v3.2)".
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. |
| 3.1 | August 2026 | + supportedParams-Schema offiziell erweitert um label, description (menschenlesbare Bedeutung — Grundlage der KI-gestützten App-Konfiguration beim Erstellen von Forscheraufträgen) und min/max (Grenzen für number-Parameter ohne values-Liste). Schema-Felder-Tabelle im ready-Abschnitt. |
| 3.2 | September 2026 | + KI-Kanal: hello mit Capability 'ki' und Feld ki (status bereit/deaktiviert/eigener-zugang, faehigkeiten, anbieter); ki-request (anbieterneutrale Chat-Anfrage: messages, zweck, modell-Klasse, temperature, maxTokens, antwortFormat, stream, timeoutMs) mit Antworten ki-chunk/ki-response/ki-error (zehn Fehlercodes), ki-cancel und optionales ki-status; Manifest-Feld ki; Host-Limits und Datenschutz-Regeln (nur Metadaten protokolliert, Schlüssel bleibt im Host). SDK: kiStatus(), kiFaehigkeiten(), kiInfo(), ki(), kiAbbrechen(), Option onKiStatus. Erster Host mit KI-Kanal: urff.app; mathe.digital sendet aktuell kein 'ki'. |
| 3.3 | September 2026 | Nur Doku, kein Breaking Change: „Zeig's am Material" — complete hakt nicht mehr selbst ab, sondern zählt als „Arbeitsmittel bestätigt", wenn das Kind „Fertig – schau nach!" drückt; boolesches state.erreicht hat Vorrang vor complete. Konvention für Ziel-Parameter (ziel, zielzahl, zielzeit, target, goal, karte in supportedParams → „Arbeitsmittel prüft selbst"). Neuer Baustein „Sammlung": ganze Sammlung als Liste im state plus save/remove-Aktionen auf archiv. |
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.