Zum Inhalt

Lektionsinhalte erstellen

Dieser Leitfaden beschreibt Schritt für Schritt, wie man ein neues Lektionsset für den Adaptive-Learner-Content-Loader aufsetzt. Wer ein Sprach- oder Themenset bauen möchte — für den Eigengebrauch oder als Beitrag zum öffentlichen Content-Pool — sollte ihn vor der ersten Lektion einmal komplett durchlesen.

Was ist ein Content-Set?

Ein Content-Set ist ein versioniertes Bündel von Lektionen, das ein Nutzer über die Set-Browser-Seite (/content) herunterladen kann. Das Content-Loader-Plugin (v1.27.0) übernimmt Discovery, Download, Caching und Versionsabgleich in beiden Speichermodi.

Ein Set hat drei Ebenen:

  1. Root-Manifest (manifest.yaml) — listet jedes Set des Repos. Wird vom Set Browser für den Quell-Katalog gelesen.
  2. Set-Manifest (sets/{set-id}/manifest.yaml) — Schwester des Root-Manifests, listet die Lektions-Dateien des konkreten Sets.
  3. Lektionsdateien (sets/{set-id}/lessons/NN-slug.json) — eine JSON-Datei pro Lektion, bei jedem Download gegen Schema v1.0 validiert.

Die mit Adaptive Learner ausgelieferten Pilot-Sets liegen im separaten Content-Repo astrapi69/adaptive-learner-content (als Geschwister-Checkout ../adaptive-learner-content ausgecheckt und vom Build über frontend/scripts/copy-bundled-content.mjs gebündelt) und eignen sich gut als Vorlage.

Sprachpaare (v1.44.0)

Jedes Content-Set deklariert das Sprach-PAAR, das es vermittelt:

  • target_language — was der Lernende LERNT (z. B. fr).
  • source_language — was der Lernende bereits SPRICHT, also die Sprache, in der die Karten-back-Felder, notes und der Theorie-Text geschrieben sind (z. B. de).

Genau das macht "Französisch für Englischsprachige" zu einem anderen Set als "Französisch für Deutschsprachige": gleiches Ziel (fr), andere Ausgangssprache (en vs. de), andere Erklärsprache. Ein Lernender sieht nur Sets, deren source_language zu einer von ihm gesprochenen Sprache passt (App-Sprache plus optionale Zusatzsprachen in Einstellungen → Lernen).

Set-IDs kodieren das Paar als {ziel}-{niveau}-from-{quelle} (z. B. fr-a1-from-de), und jedes Set deklariert einen path, der auf sein Ausgangssprach-Verzeichnis zeigt (sets/de/fr-a1). Ein Set trägt außerdem title (in der Ausgangssprache, was der Lernende liest) und title_native (in der Zielsprache, als Zweittitel).

Beide Codes müssen ISO-639-1 (zwei Buchstaben) sein, und source_language muss sich von target_language unterscheiden. Sets vor v1.2 ohne diese Felder laden weiterhin: der alte language-Schlüssel wird als target_language akzeptiert, und source_language fällt auf en zurück.

Verzeichnislayout

Der Baum ist nach AUSGANGSSPRACHE, dann Ziel+Niveau organisiert:

mein-content-repo/
  manifest.yaml               # Root: listet jedes Set (mit path + Paar)
  sets/
    de/                       # Ausgangssprache: Deutsch
      fr-a1/                  # Ziel Französisch, Niveau A1  -> ID fr-a1-from-de
        manifest.yaml         # Set: listet die Lektionen
        lessons/
          01-begruessung.json
          ...
        assets/               # optionale Bilder / Audio
    en/                       # Ausgangssprache: Englisch
      fr-a1/                  # -> ID fr-a1-from-en
        ...

Manifest-Format

Beide Manifest-Dateien (Root + Set) verwenden die gleiche Form mit schema_version: '1.0'. Pflichtfelder:

schema_version: '1.0'
name: Mein Englisch-B1-Set
description: >-
  Optionale Langbeschreibung.
sets:
  - id: language-en-b1        # slug-sicher, eindeutig
    title: Englisch B1 (Fortgeschrittene)
    language: en              # BCP-47 (z.B. en, fr, zh-Hans)
    level: B1                 # CEFR für Sprachen, frei für andere Domänen
    version: '1.0.0'          # Semver — pro Set-Release erhöht
    lesson_count: 12
    domain: language          # 'language' / 'math' / 'programming' / ...
    description: >-
      Optionale Set-Beschreibung.
    tags:
      - intermediate
      - business
metadata:
  author: Dein Name
  license: CC-BY-SA-4.0       # oder die Lizenz deiner Wahl

Das Set-Manifest listet zusätzlich jede Lektionsdatei:

metadata:
  lessons:
    - 01-intro.json
    - 02-articles.json
    - ...

Der Content-Loader iteriert metadata.lessons in der gegebenen Reihenfolge; die Dateinamen auf der Festplatte sind irrelevant — nur die Manifest-Reihenfolge zählt.

Lektionsschema (v1.0)

Jede Lektion ist eine einzelne JSON-Datei. Top-Level-Struktur:

{
  "id": "01-greetings",
  "title": "Begrüßungen",
  "description": "Optionale 1-2-Satz-Zusammenfassung.",
  "estimated_minutes": 12,
  "cards": [ ... ],
  "steps": [ ... ]
}

Cards

Eine Card ist die kleinste lernbare Einheit — typischerweise ein einzelner Begriff oder ein Konzept. Jede Card hat eine stabile id (aus Übungen referenziert) und ein front/back-Paar:

{
  "id": "art-le",
  "front": "le",
  "back": "der (männlich Singular)",
  "notes": "Vor konsonantenanfangenden männlichen Substantiven. **le chat**, **le livre**.",
  "tags": ["article", "definite"]
}

notes akzeptiert Markdown. Nutze sie für Ausspracheregeln, Falsche-Freunde-Warnungen, Ausnahme-Hinweise — alles, was die Langzeitspeicherung verbessert. tags steuern das SRS-Filtering.

Steps

Eine Lektion ist eine Schritt-für-Schritt-Sequenz, jeder Schritt entweder THEORY (ein Markdown-Block) oder EXERCISE (eine der vier Übungstypen):

{
  "id": "intro",
  "type": "theory",
  "title": "Warum Artikel wichtig sind",
  "body": "# Artikel im Französischen\n\nJedes französische Nomen hat ein Geschlecht..."
}

Ein Theorie-Step kann optional einen Beispiel-Link tragen (Schema v1.4, additiv — bestehende Lektionen bleiben ohne ihn gültig). Wenn vorhanden, rendert der Viewer darunter einen Button zum Öffnen des Beispiels:

{
  "id": "intro",
  "type": "theory",
  "body": "Die Korrelation misst den Zusammenhang...",
  "example_url": "https://example.com/correlation-visualizer",
  "example_label": "Interaktive Visualisierung"
}
  • example_url (optional): muss eine http(s)-URL sein.
  • example_label (optional): der Link-Text; leer wird zu einem lokalisierten „Beispiel ansehen".

Oder eine Übung:

{
  "id": "ex-match-greetings",
  "type": "exercise",
  "title": "Begrüßungen zuordnen",
  "exercise": {
    "id": "ex-match-greetings",
    "type": "matching",
    "prompt": "Ordne jede Begrüßung ihrer Übersetzung zu.",
    "card_ids": ["bonjour", "salut"],
    "pairs": [
      {"left": "Bonjour", "right": "Hallo"},
      {"left": "Salut", "right": "Hi"}
    ]
  }
}

Übungstyp-Referenz

matching

Drag-pair-Übung. Der Renderer mischt vor der Anzeige.

{
  "id": "ex-id",
  "type": "matching",
  "prompt": "Ordne jedem französischen Nomen seinen Artikel zu.",
  "card_ids": ["noun-1", "noun-2"],
  "pairs": [
    {"left": "chat", "right": "le"},
    {"left": "chaise", "right": "la"}
  ]
}

Jedes Pair muss genau zwei Schlüssel haben: left + right.

picture_choice

Multiple-Choice mit Bildern. ≥ 2 Bilder, genau eines als richtig markiert.

{
  "id": "ex-id",
  "type": "picture_choice",
  "prompt": "Welche Begrüßung passt zum Abend?",
  "card_ids": ["card-1"],
  "images": [
    {"src": "assets/img/morning.png", "label": "Bonjour"},
    {"src": "assets/img/evening.png", "label": "Bonsoir", "is_correct": "true"}
  ],
  "hint": "Optionaler Markdown-Tipp auf Knopfdruck.",
  "distractors": ["Bonjour"]
}

Wichtig: is_correct ist ein String "true", kein JSON-Boolean.

Zeigt der src-Pfad auf eine nicht vorhandene Datei, fällt der Renderer auf das label zurück — picture_choice funktioniert also auch ohne Illustrations-Assets.

free_text

Antwort eintippen. Der Renderer matched erst exakt, dann Levenshtein-tolerant.

{
  "id": "ex-id",
  "type": "free_text",
  "prompt": "Wie sagt man 'Danke' auf Französisch?",
  "card_ids": ["card-merci"],
  "accept": ["Merci", "merci", "MERCI"],
  "hint": "Beginnt mit M.",
  "distractors": ["Bonjour", "Salut"]
}

accept[0] ist die kanonische Antwort, die bei einem falschen Versuch angezeigt wird. Liste ≥ 3 Varianten auf, um Groß/Klein- schreibung + Interpunktion abzudecken; Whitespace wird vom Renderer normalisiert.

word_tiles

Kacheln in die richtige Reihenfolge bringen. Der Renderer mischt vor der Anzeige.

{
  "id": "ex-id",
  "type": "word_tiles",
  "prompt": "Bring die Kacheln in die Reihenfolge: Ich sehe eine Katze.",
  "card_ids": ["card-1"],
  "tiles": ["Je", "vois", "un", "chat"],
  "hint": "Gleiche Wortreihenfolge wie im Deutschen."
}

Falls mehrere Wortreihenfolgen korrekt sind, ergänze accept_orderings:

{
  "tiles": ["Je", "vois", "un", "chat"],
  "accept_orderings": [
    [0, 1, 2, 3],
    [0, 1, 3, 2]
  ]
}

Jede Reihenfolge ist eine Permutation der Tile-Indizes.

cloze (Phase 52 / v1.35.0 — Schema 1.1)

Lückentext mit sichtbaren ___-Markern im Satz. Jeder ___ entspricht einem Eintrag in blanks[] (Zuordnung von links nach rechts; der Loader prüft sentence.count("___") == len(blanks)).

{
  "id": "ex-id",
  "type": "cloze",
  "prompt": "Setze den unbestimmten Artikel ein.",
  "card_ids": ["art-un", "noun-chat"],
  "sentence": "Je vois ___ chat dans le jardin.",
  "blanks": [
    {
      "accept": ["un"],
      "hint": "männlicher unbestimmter Artikel",
      "placeholder": "?"
    }
  ],
  "cloze_mode": "type",
  "distractors": ["le", "la", "les"],
  "hint": "*un* ist der männliche unbestimmte Artikel."
}

Render-Modi — pro Übung über cloze_mode gesetzt:

  • "type" (Standard, wenn nicht gesetzt): pro Lücke ein <input>. Validiert mit demselben NFC + Levenshtein-≤-1- Matcher wie free-text, sodass Autorinnen nur semantische Varianten auflisten müssen (keine Tippfehler).
  • "select": pro Lücke ein <select>. Optionen aus accept[0] + distractors der Übung, pro Lücke mit stabilem Seed gemischt. Erfordert nicht-leere distractors — der Schema-Validator weist cloze_mode: "select" ohne sie ab.

Mehrere Lücken pro Cloze sind unterstützt: jeder ___ im Satz wird der Reihe nach auf den nächsten Eintrag in blanks abgebildet. Jede Lücke kann eigenen Hint + Placeholder + Accept-Liste haben. Das Element-SRS fächert pro Lücke einen ElementAttempt auf — wer Lücke A fließend füllt, aber Lücke B ständig verfehlt, bekommt eine lückengranulare Mastery- Verfolgung.

Token-Rollen auf Cards (Phase 52I / v1.35.0) — optionale Card-Metadaten, mit denen der Cloze-Generator zur Laufzeit (Review-Sessions + die Korrektur-Runde am Lektionsende) eine semantisch bedeutsame Lücke wählen kann:

{
  "id": "art-un",
  "front": "un chat",
  "back": "eine Katze",
  "tags": ["article"],
  "token_roles": [
    {"token": "un", "role": "article"}
  ]
}

Geschlossene Enum von Rollen: article / verb / noun / adjective / preposition / gender_marker / tense_marker. Eine Rolle hinzuzufügen ist ein Minor-Schema-Version-Bump — nicht inline erweitern.

Übungsrichtung (v1.46.0 / EXP-018)

Jede Übung akzeptiert ein optionales Feld direction, das angibt, in welche Richtung die Lernenden die Karte üben:

  • target_to_source (Standard) — REZEPTIV: die Zielsprache wird gezeigt, die Quellsprache wird erkannt (leichter).
  • source_to_target — PRODUKTIV: die Quellsprache wird gezeigt, die Zielsprache wird produziert (schwerer).
  • both / random — überlässt dem Renderer / adaptiven Generator die Wahl einer konkreten Richtung pro Versuch.
{
  "type": "matching",
  "direction": "source_to_target",
  "card_ids": ["bonjour"],
  "pairs": [{ "left": "Bonjour", "right": "Guten Tag" }]
}

Das Feld ist additiv — das Schema bleibt bei Version 1.2, und Lektionen ohne direction verhalten sich genau wie zuvor (rezeptiv). Das SRS verfolgt die Beherrschung pro Richtung: eine rezeptiv gemeisterte Karte ist noch nicht produktiv gemeistert. Cloze-Übungen sind kontextgebunden und ignorieren direction. Für eine Schwierigkeitsprogression hält man frühe Lektionen rezeptiv und führt source_to_target in späteren Lektionen ein (genau das macht der gebündelte Pilot-Inhalt).

Annotationen für den adaptiven Lektions-Generator (v1.36.0+)

Der adaptive Lektions-Generator aus Phase 53 (/adaptive-lesson/:setId, F-114) kombiniert die vorhandenen Übungen neu, um die spezifischen Schwächen der Lernenden gezielt zu adressieren. Der Generator funktioniert ohne zusätzliche Annotationen, zwei Felder machen ihn jedoch deutlich smarter:

  1. Breitere token_roles-Abdeckung auf Karten. Der Generator nutzt token_roles, um:
  2. Semantisch sinnvolle Lücken zu wählen, wenn aus Fehlern Cloze-Varianten erzeugt werden (bereits in v1.35.0)
  3. Fehler als article_gender / verb_conjugation zu klassifizieren, für die "Übungsschwerpunkt"-Chips im Dashboard (53E)
  4. ALTERNATIVE Übungen zu finden, die dasselbe Element testen, wenn die ursprüngliche Übung falsch war (53D Variations-Logik — findet Kandidaten, deren Karte einen passenden token_roles-Eintrag hat)

Füge JEDEN Karten, die eine eigene grammatische Einheit lehrt (Artikel, konjugierte Verbformen, geschlechtsbezogene Substantive), einen token_roles-Eintrag hinzu. Kosten: ein zusätzlicher JSON-Eintrag pro Karte; Nutzen: deutlich reichhaltigere adaptive Generierung.

  1. Karten-Tags wie tags: ["article", "masculine"] werden vom Fehler-Klassifizierer als Fallback gelesen, wenn token_roles fehlt. Sie ersetzen nicht token_roles — sie sind eine günstige Halbweg-Annotation.

Was wir noch NICHT brauchen (auf einen zukünftigen Schema-Bump verschoben):

  • related_cards-Querverweise zwischen Karten aus verschiedenen Lektionen
  • Schwierigkeits-Ratings pro Übung (der Generator schätzt Schwierigkeit aktuell aus exercise.type ab)
  • Pro-Karte Beispielsätze in notes, parsebar als alternative Cloze-Kontexte (der Cloze-Generator nutzt ausschließlich front)

Faustregel: füge token_roles zu jeder Karte hinzu, die einen grammatischen Token lehrt. Das ist die mit Abstand wirkungsvollste Autoren-Gewohnheit für das adaptive System.

Assets (Bilder, die ein Set mitbringt) — v1.37.0+

Picture-Choice-Übungen und Karten-Cover-Bilder kommen aus zwei Quellen: 1. Autoren-Asset-Dateien, im Set-Manifest deklariert und neben dem Lektions-JSON ausgeliefert 2. Platzhalter-SVGs, vom Runtime erzeugt, wenn kein Asset existiert (Farbtafeln für Farbwörter, große Ziffern für Zahlen, Avatar-Stil für alles andere)

Wenn du ein Set ohne Assets veröffentlichst, funktioniert Picture-Choice trotzdem — der Platzhalter-SVG-Generator deckt Farben + Zahlen automatisch ab und fällt für alles andere auf einen deterministischen Avatar zurück.

Verzeichnis-Layout

Innerhalb des Set-Verzeichnisses liegen Assets unter assets/:

sets/
  language-fr-a1/
    manifest.yaml
    lessons/
      01-greetings.json
      02-numbers.json
      ...
    assets/
      img/
        chat.png
        chien.png
        oiseau.png

Manifest-Deklaration

Jedes Asset muss im Set-Manifest deklariert werden, damit der Downloader weiß, was er holen soll:

sets:
  - id: language-fr-a1
    title: French A1
    language: fr
    level: A1
    version: '1.0.0'
    lesson_count: 10
    assets:
      - path: img/chat.png
        size_kb: 45
      - path: img/chien.png
        size_kb: 38

Der path ist relativ zum assets/-Verzeichnis des Sets (NICHT zum Lektions-JSON). Im Lektions-JSON referenzieren Picture-Choice-Übungen Assets MIT dem assets/-Präfix:

{
  "type": "picture_choice",
  "prompt": "Welches ist 'chat'?",
  "images": [
    {"src": "assets/img/chat.png", "label": "Katze", "is_correct": "true"},
    {"src": "assets/img/chien.png", "label": "Hund"}
  ]
}

Das Frontend entfernt den assets/-Präfix automatisch beim Aufruf des Asset-Resolvers, sodass das Lektions-JSON in der für Autoren intuitiven Form bleibt.

Größen- + Format-Limits

  • Pro-Asset-Limit: 500 KiB. Der Manifest-Validator weist Assets ab, deren deklariertes size_kb dieses Limit überschreitet. Der Downloader weist auch Assets ab, deren tatsächliche Bytegröße die Deklaration um mehr als 10% überschreitet — hält das Manifest ehrlich.
  • Pro-Set Soft-Limit: 10 MiB Gesamtgröße. Der Validator warnt, lehnt aber nicht ab.
  • Akzeptierte Formate: .png / .jpg / .jpeg / .webp / .svg. Kein GIF (animierte Inhalte lenken ab), kein BMP (keine Kompression). Für Fotos bevorzugt WebP — deutlich kleiner als PNG bei vergleichbarer Qualität. Für Icons + Diagramme bevorzugt SVG — skaliert sauber + winzige Dateigröße.

Größen-Empfehlungen

Picture-Choice-Kacheln werden bis maximal 150x150 px auf dem Desktop und 100x100 px auf Mobile gerendert (object-fit: contain). Quellbilder mit 300x300 px liefern auf Retina- Bildschirmen das beste Ergebnis ohne unnötigen Datenbedarf. PNGs über 150 KiB sehen selten besser aus als ein gut komprimiertes WebP halber Größe.

Wann der Runtime-Platzhalter ausreicht

Drei Lektionsarten, bei denen der Runtime-Platzhalter so gut ist, dass Autoren-Bilder keinen Lerngewinn bringen:

  • Farb-Lektionen (rouge / rojo / rot / red): der Platzhalter-Generator erzeugt eine farbige Hex-Kachel passend zum Farbnamen. Autoren-Kacheln sind redundant.
  • Zahlen-Lektionen (7 / 42 / 1492): der Platzhalter rendert die Ziffern groß + zentriert. Autoren-Bilder hätten nur bei nicht-arabischen Ziffernsystemen Sinn.
  • Abstrakte Konzepte ohne offensichtliche visuelle Darstellung (patience, liberté): der Avatar-Platzhalter liefert einen klaren visuellen Anker, ohne eine umstrittene Icon-Wahl zu erzwingen.

Für alles andere (Tiere, Objekte, Essen, Orte, Körperteile) helfen Autoren-Bilder messbar bei Erkennen + Erinnern.

Qualitäts-Checkliste

Vor dem PR für eine neue Lektion prüfen:

  • [ ] 3-5 Theorie-Schritte + 8-12 Übungen pro Lektion
  • [ ] Mindestens 3 Übungstypen vertreten (matching, picture-choice, free-text, word-tiles oder cloze — cloze ab v1.35.0)
  • [ ] Theorie-Schritte ≤ 200 Wörter je Schritt
  • [ ] Free-Text-Übungen: ≥ 3 Akzept-Varianten + ≥ 3 Distraktoren
  • [ ] Word-Tiles: ≥ 3 Kacheln je Übung
  • [ ] estimated_minutes: 10-15 (realistisch, nicht idealisiert)
  • [ ] Distraktoren sind falsch-aber-plausibel — semantisch verwandt, nie zufällig
  • [ ] Card-Notes liefern echten Mehrwert (Aussprache, falsche Freunde, Ausnahme-Flag)
  • [ ] Progressive Struktur: spätere Konzepte bauen auf früheren im selben Set auf
  • [ ] Kulturelle Genauigkeit: realer Sprachgebrauch, nicht nur Lehrbuch-Floskeln
  • [ ] Schema-Validierung: die Lektion lädt sauber via dict_to_lesson() (siehe Lokales Testen)
  • [ ] Card-ID-Integrität: jedes exercise.card_ids[i] existiert in cards[] der Lektion
  • [ ] Sprachpaar: target_language + source_language gesetzt (ISO 639-1, verschieden), title_native vorhanden

Validierung (zwei Ebenen, v1.44.0)

Inhalte werden durch zwei Validierungsebenen mit den GLEICHEN Prüfungen abgesichert:

  1. In der App, vor dem Teilen. Beim Teilen über Meine Lektionen → Für die Community bereitstellen läuft zuerst eine regelbasierte Prüfung (immer, ohne KI). Sie erzwingt die Mindestwerte unten; ein Set darunter kann nicht geteilt werden. Besteht es und ist ein KI-Schlüssel konfiguriert, kann der Lernende OPTIONAL eine ergänzende KI-Prüfung starten (Übersetzungsgenauigkeit, Distraktor-Plausibilität, Grammatik, Niveau, kulturelle Sensibilität, Natürlichkeit). Der KI-Schritt ist nie automatisch, erfordert ausdrückliche Zustimmung (der Lektionsinhalt wird an den konfigurierten Anbieter gesendet) und blockiert das Teilen nie — die regelbasierte Prüfung ist das Tor.
  2. In der CI des Content-Repos. Ein Pull Request an astrapi69/adaptive-learner-content führt scripts/validate_content.py aus (gespiegelt unter docs/ci/adaptive-learner-content/) und prüft jedes Set mit denselben Regeln, damit ein manueller PR das Tor nicht umgeht.

Qualitäts-Mindestwerte (hartes Tor): ≥ 5 Übungen pro Lektion, ≥ 2 Übungstypen, ≥ 1 Theorie-Schritt, Free-Text ≥ 2 akzeptierte Antworten + Distraktoren, Matching ≥ 3 Paare, Picture-Choice mit Distraktoren, keine leeren Karten-Vorder-/Rückseiten und (bei nicht-lateinischen Ausgangsschriften) Karten-Rückseiten in der Ausgangsschrift. Das sind Mindestwerte, keine Ziele — die Checkliste oben verlangt mehr.

Lokales Testen

Der Schema-Validator des Content-Loaders läuft im Rahmen von make test. Eine einzelne Lektion von Hand validieren:

cd plugins/adaptive-learner-plugin-content-loader
poetry run python -c "
import json, sys
from adaptive_learner_content_loader.schema import dict_to_lesson
path = '../adaptive-learner-content/sets/en/fr-a1/lessons/01-greetings.json'
with open(path) as f:
    lesson = dict_to_lesson(json.load(f))
print(f'OK: {lesson.id} — {len(lesson.cards)} Cards, {len(lesson.steps)} Steps')
"

Alle Lektionen eines Content-Repos auf einmal validieren — mit dem Validator des Content-Repos (dasselbe Skript, das dessen CI bei jedem PR ausführt):

cd ../adaptive-learner-content
python3 scripts/validate_content.py

Er findet jedes Set unter sets/{source}/{target-level}/ und prüft das Schema plus die Qualitäts-Mindestwerte (≥5 Übungen, ≥2 Übungstypen, ≥1 Theorieschritt, Freitext-Akzepte + Distraktoren, Matching-Paare, keine leeren Karten, Karten-ID-Integrität). Neue Lektionen werden automatisch erkannt — keine Test-Änderung nötig.

PR-Workflow

Sobald dein Set fertig ist:

  1. Öffne einen PR gegen das Haupt-Repo (für Sets, die mit der App ausgeliefert werden sollen), ODER
  2. Lege ein eigenes Content-Repo unter deinem GitHub-Account an und konfiguriere den Content-Loader über backend/config/plugins/content-loader.yaml (unter default_sources).

Der Content-Loader unterstützt jedes öffentliche GitHub-Repo als Quelle. Private Repos benötigen ein Personal Access Token, das über die Drei-Schichten-Schlüsselverwaltung gesetzt wird (~/.config/adaptive_learner/secrets.yaml).

Häufige Stolperfallen

Card-ID-Verweise: Jeder card_ids-Eintrag in einer Übung muss in cards[] der Lektion existieren. Kopierst du eine Übung zwischen Lektionen und vergisst die zugehörige Card mitzunehmen, schlägt die Validierung fehl.

Slug-sichere IDs: Alle IDs (Lesson, Card, Step, Exercise) müssen ^[a-z0-9]+(-[a-z0-9]+)*$ matchen. Keine Unterstriche, keine Apostrophe, keine Großbuchstaben, keine führenden/abschließenden Bindestriche.

is_correct: "true": Es ist ein String, kein JSON-Boolean. Das Schema verlangt explizit "true", weil die picture_choice- Felder intern als dict[str, str] modelliert sind.

Zusätzliche Felder: Jedes Modell hat extra="forbid". Ein nicht-dokumentiertes Feld führt zur Ablehnung der gesamten Lektion. Halte dich an die dokumentierten Felder.

Theory-Body: Theory-Steps benötigen ein nicht-leeres body-Feld (Markdown). Exercise-Steps dürfen kein body tragen — nutze stattdessen den prompt der Übung.

Referenz: die Pilot-Sets

Die beiden mit Adaptive Learner ausgelieferten Sets sind die kanonischen Referenzen:

  • sets/en/fr-a1/ — Französisch A1 für Englischsprachige (10 Lektionen, ~2 Stunden); sets/de/fr-a1/ ist das deutschsprachige Pilot-Set.
  • sets/en/es-a1/ + sets/de/es-a1/ — Spanisch A1 (15 Lektionen je Quellsprache), im adaptive-learner-content-Repo.

Beide folgen den in diesem Leitfaden beschriebenen Konventionen. Eine vollständige Lektion durchzulesen ist der schnellste Weg, die Struktur zu verinnerlichen.


Weg zur Community-Beteiligung (v1.42.0)

Du musst Lektionen nicht von Grund auf von Hand erstellen. Der schnellste Weg, etwas beizutragen, ist, eine Lektion in der App zu erstellen und zu teilen:

  1. Importiere einen Chat und analysiere ihn, dann Als Offline-Lektion speichern (oder beende eine adaptive Lektion und Diese Lektion speichern?). Die Lektion erscheint unter Meine Lektionen im Set-Browser.
  2. Klicke bei „Meine Lektionen" auf Als Content-Set exportieren, um ein Content-Set als .zip herunterzuladen (Manifest + Lektionen). Exporte enthalten nur den Lektionsinhalt — keinen Fortschritt, keine Fehlerhistorie, nichts Persönliches.
  3. Klicke auf Für die Community bereitstellen, um einen vorausgefüllten Pull Request im Inhalts-Repository zu öffnen — die Lektions-JSON wird am richtigen Pfad im Baum committet, kein .zip-Anhang nötig.
  4. Die CI des Repos validiert den PR automatisch; ein Maintainer prüft die Lektion, bringt das Manifest (id, title, language, level, tags) in Einklang mit den obigen Konventionen und führt ihn unter sets/ zusammen. Nach dem Merge können alle sie aus dem Set-Browser herunterladen.

Das ist der soziale Weg: Die Prüfung ist manuell (ein Maintainer kuratiert jede Ergänzung — nichts wird automatisch veröffentlicht), und der gesamte Ablauf braucht nur GitHub. Erzeugte Lektionen werden bereits gegen das Schema validiert, sodass eine beigetragene Lektion meist nur etwas Manifest-Feinschliff braucht.

Teilen-Assistent, Variationen und Autoren-Credit (Phase 64)

Eine Lektion aus Meine Lektionen zu teilen öffnet einen vierstufigen Assistenten, statt direkt zu GitHub zu springen:

  1. Vorschau + Platzierung. Die App berechnet genau, wo die Lektion im Baum landet (sets/{quelle}/{ziel}-{niveau}/) und einen automatisch nummerierten Dateinamen ({nn}-{slug}.json, die nächste Nummer nach den bestehenden Lektionen). Ein ganz neues Paar + Niveau zeigt "Neues Set! Du bist der Erste."
  2. Duplikat-Prüfung. Die Lektion wird mit den bereits in diesem Pfad vorhandenen Lektionen verglichen (Karten- und Übungs-Überschneidung — beratend, niemals blockierend). Wenn etwas Ähnliches existiert, kannst du:
  3. Als Variation teilen — die Lektion wird mit variation_of: "{original_id}" plus einer optionalen variation_note markiert ("Wie unterscheidet sich deine Version?").
  4. Nur die neuen Übungen vorschlagen (bei Beinahe- Duplikaten) — der Assistent extrahiert genau die Übungen, die dem Original fehlen, samt der zugehörigen Karten, als Ergänzungs-Variation.
  5. Qualitäts-Zusammenfassung. Die Befunde des regelbasierten Validators (plus die optionale KI-Prüfung); Warnungen werden angezeigt, blockieren aber nie.
  6. Teilen + Feiern. Ein Klick öffnet den GitHub-Pull-Request (Datei-Editor bei kleinen Lektionen, Upload-Seite bei großen), und die App bedankt sich mit einer kleinen Feier.

Variations- und Credit-Felder (Schema 1.3, alle optional)

{
  "variation_of": "10-passe-compose",
  "variation_note": "Mehr Übungen zur Angleichung",
  "contributed_by": "Maria S.",
  "contributed_at": "2026-06-01T14:30:00Z"
}

Alle vier sind additiv und optional; Lektionen ohne sie verhalten sich genau wie zuvor. contributed_by wird gesetzt, wenn der Autor beim Teilen den Credit aktiviert (ein Feld "Dein Name (optional)", das lokal für das nächste Mal gemerkt wird). Ist es vorhanden, zeigt der Viewer eine dezente Zeile "Bereitgestellt von {name}" unter dem Titel, und der Pull-Request-Text führt den Autor in seiner Metadaten-Tabelle auf.

Beitrags-Historie und Lücken

Geteilte Lektionen werden lokal gemerkt (kein Konto nötig) unter Meine Beiträge mit einem Zähler und einer Community-Beitragende-Auszeichnung ab fünf geteilten Lektionen. Der Set-Browser zeigt außerdem Fehlende Lektionen — ermutigende Vorschläge für das nächste CEFR-Niveau eines bestehenden Paars oder eine Zielsprache, die für eine Ausgangssprache existiert, für eine andere aber fehlt ("Kannst du helfen?").


Verwandte Seiten