Files
lernbar/ENTSCHEIDUNGEN.md
T
Florian WildeandClaude Opus 5.5 44e6c8bb79 Module abschaltbar machen
Merkkarten, Praxisaufgaben, Checklisten, Berichtsheft, Lernzielkontrollen
und Feedbackgespraech lassen sich unter Verwaltung -> Module fuer die ganze
Lern-Bar ab- und einschalten. Wochenplan, Handouts, Glossar, Auswertung und
Verwaltung bleiben immer an.

Gesperrt wird an drei Stellen, nicht im Layout (Next rendert Layouts beim
Seitenwechsel darunter nicht neu): Seiten mit erfordereModul, Routen fuer
PDF, Anhaenge und Lernstand mit modulAn, alle 37 Serveraktionen der Module
mit pruefeModul. Die Anmeldepruefung kommt jeweils zuerst, damit ein
Unangemeldeter nicht am 404 abliest, was abgeschaltet ist.

Ein abgeschaltetes Modul traegt nirgends bei: Wochenplan, Fortschritt,
Was-wartet, Mein Stand, Ergebnisverlauf, Rahmenplanabdeckung, Vorschlaege
fuers Berichtsheft, Karten im Handout, Kurzbefehle im Manifest. Die
Lernstand-Schnittstelle antwortet mit 410, das Geraet verwirft daraufhin
seine Offline-Warteschlange. Abschalten loescht nichts.

Gespeichert in modul_einstellung (Migration 0010); fehlt die Zeile, ist das
Modul an, eine bestehende Datenbank verhaelt sich also wie vorher.

12 Tests fuer die Regeln, Abschnitt A8 im Abnahmelauf, Anleitung Kapitel 9
mit Bildschirmfoto, README, E43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:58:32 +02:00

25 KiB
Raw Blame History

Entscheidungen

Kurze Notizen zu Festlegungen, die im Auftrag offen waren. Jede Notiz nennt die Lage, die Wahl und den Grund.


E1 · Stack: Next.js 15 mit App Router

Der Auftrag verweist auf eine Stackvorgabe im „Kopf des Prompts", die nicht mitgeliefert wurde. Auf Rückfrage: Next.js 15 (App Router), React 19, TypeScript. Datenzugriff über Drizzle ORM auf better-sqlite3.

Serveraktionen ersetzen eine eigene REST-Schicht. Das hält die Rechteprüfung dort, wo die Daten geholt werden, statt sie über eine API zu verteilen.

E2 · Zeitstempel als ISO-8601 in UTC

Der Auftrag verlangt „alle Zeitstempel mit Zeitzone". Gespeichert wird 2026-09-10T07:15:00.000Z, also ein eindeutiger Zeitpunkt in UTC. Angezeigt wird durchgehend in Europe/Berlin.

Grund: UTC-Strings sortieren sich lexikografisch richtig, was bei SQLite ohne echten Zeitstempeltyp entscheidend ist. Ein gespeicherter Ortsversatz (+02:00) würde beim Wechsel zwischen Sommer- und Winterzeit falsch sortieren.

Reine Kalenderdaten – Berichtszeitraum, Tagesnotiz, Ausbildungsbeginn – sind YYYY-MM-DD ohne Uhrzeit, weil sie auch keine haben.

E3 · Eigener Migrationslauf statt drizzle-kit

Migrationen sind handgeschriebene SQL-Dateien unter src/lib/db/migrationen/, die beim Start der Reihe nach genau einmal angewendet und mit SHA-256-Prüfsumme vermerkt werden.

Grund: volle Kontrolle über Indizes und CHECK-Bedingungen (Statuswerte sind Aufzählungen, keine freien Zeichenketten), und beim Start ist im Protokoll sichtbar, was passiert ist. Wird eine bereits angewendete Datei nachträglich verändert, bricht der Start mit klarer Meldung ab.

Der Preis: das Drizzle-Schema muss von Hand zu den SQL-Dateien passen. Dagegen läuft ein Test, der die migrierte Datenbank gegen das Schema abgleicht.

E4 · Schlüssel sind UUIDs, keine laufenden Nummern

Textspalten mit crypto.randomUUID(). Läuft auf SQLite und PostgreSQL gleichermaßen, und beim Zurückspielen einer Sicherung gibt es keine Kollisionen mit zwischenzeitlich vergebenen Nummern.

E5 · Debian-slim als Grundlage des Abbilds, nicht Alpine

better-sqlite3 und @node-rs/argon2 bringen für glibc fertige Binärdateien mit, für musl nicht. Auf Alpine müsste im Abbild übersetzt werden – längerer Bau, größere Bauschicht, mehr Fehlerquellen. Der Größenunterschied im Laufzeitabbild ist bei einem selbst gehosteten Dienst ohne Belang.

E6 · Passwortprüfung mit gleichbleibender Antwortzeit

Bei unbekannter E-Mail-Adresse wird trotzdem einmal gegen einen festen Argon2-Wert gerechnet. Sonst verrät die Antwortzeit, welche Adressen es gibt. Die Fehlermeldung ist in beiden Fällen dieselbe.

E7 · Anhänge liegen im Dateisystem, nicht in der Datenbank

Unter <daten>/anhaenge/, in der Datenbank steht nur der Verweis. Hält die Datenbankdatei klein und die Sicherung schnell; das Archiv nimmt beides mit.

E8 · Sicherung als Shell-Skript, nicht als Node-Werkzeug

scripts/im-container/sicherung.sh läuft im Laufzeitabbild, das kein TypeScript kennt, und auf dem Entwicklungsrechner unter Git Bash. Eine zweite Fassung in TypeScript wäre eine zweite Fehlerquelle. Die konsistente Kopie der Datenbank entsteht über die Sicherungsschnittstelle von SQLite – ein cp wäre bei aktivem WAL-Modus nicht verlässlich.

E9 · Kein main-Zweig ohne Freigabe

Die verbindliche Arbeitsweise verbietet Commits direkt auf main. Die Erstfassung entsteht deshalb vollständig auf feat/lernbar-erstfassung. main bleibt leer, bis eine Freigabe erfolgt.


E10 · Rahmenplan: A1–A10, B1–B3, C1–C5

Der Auftrag nennt die Berufsbildpositionen A 1 bis A 10, B 1 bis B 3 und C 1 bis C 4. Die Verordnung selbst gliedert anders: Sie deckt alle vier Fachrichtungen ab und hat deshalb sechs Abschnitte. Übersetzt:

Kürzel hier Abschnitt der FIAusbV Inhalt
A1 – A10 Abschnitt A fachrichtungsübergreifend
B1 – B3 Abschnitt C Fachrichtung Systemintegration
C1 – C5 Abschnitt F integrativ zu vermitteln

Abweichung: Abschnitt F hat fünf Positionen, nicht vier. Die fünfte („Vernetztes Zusammenarbeiten unter Nutzung digitaler Medien") ist 2020 neu dazugekommen. Sie wegzulassen würde ausgerechnet in der Abdeckungsansicht eine Lücke erzeugen, die gegenüber der IHK zählt. Deshalb sind alle fünf drin.

Jede Position trägt im Feld verordnung ihre amtliche Fundstelle, damit die Übersetzung nachvollziehbar bleibt.

E11 · Kennung einer Merkkarte: Handout-Kürzel plus laufende Nummer

Eine Karte heißt N3-1, N3-2 und so weiter – abgeleitet aus dem Handout und der Reihenfolge im YAML-Kopf. Damit steht die Kennung fest, ohne dass sie in der Datei gepflegt werden muss.

Der Preis: Wird eine Karte mittendrin eingefügt oder gelöscht, verschieben sich die Kennungen der folgenden, und ihr Lernstand rutscht mit. Neue Karten gehören deshalb ans Ende der Liste. Bei einem Handout mit ein bis drei Karten ist das zumutbar; eine gepflegte Kennung je Karte wäre mehr Aufwand für die Person, die Inhalte schreibt, als sie einbringt.

Der Auftrag lässt die Wahl zwischen automatischer Verlinkung und einer Erklärung beim Überfahren mit der Maus. Es wird beides, aber der Link führt: Ein reiner title-Hinweis ist am Handy nicht erreichbar und mit der Tastatur nur mit Mühe. Der Verweis zeigt auf die Stelle im Glossar und trägt die Erklärung zusätzlich im title.

Zurückhaltung ist eingebaut: Jeder Begriff wird höchstens einmal je Seite verlinkt, und nie innerhalb von Code, Überschriften, Tabellenköpfen oder bestehenden Verweisen. Ein Text, in dem jedes zweite Wort unterstrichen ist, liest sich schlechter als einer ganz ohne.

E13 · Kein gray-matter

Der YAML-Kopf einer Markdown-Datei wird selbst herausgelöst (zehn Zeilen). Gründe: gray-matter merkt sich Ergebnisse und liefert beim zweiten Einlesen desselben Textes ein Objekt ohne das Feld matter – im Betrieb fällt das nie auf, im Test sofort. Und der Zeilenversatz für die Fehlermeldungen ist so nachweislich richtig statt nachgebaut.

E14 · SM-2 mit drei Bewertungsstufen statt sechs

Das Original arbeitet mit einer Skala von 0 bis 5. In der Praxis unterscheidet niemand ehrlich zwischen „fast richtig" und „mit Mühe richtig" – und eine Selbstbewertung, die man nicht ehrlich abgeben kann, verfälscht die Rechnung.

Deshalb: sitzt / unsicher / nochmal. Die Leichtigkeit bewegt sich weiterhin zwischen 1,3 und 2,8, die Abstände folgen 1, 3, dann Abstand mal Leichtigkeit. Gedeckelt wird bei 180 Tagen – die Ausbildung dauert drei Jahre, weiter zu planen bringt nichts.

E15 · „nochmal" bringt die Karte in derselben Sitzung wieder

Erste Fassung legte sie nur auf den nächsten Tag. Das ist falsch: Wer „nochmal" drückt, will die Karte jetzt noch einmal sehen. Sie wird deshalb hinten an den laufenden Stapel gehängt.

E16 · Der tägliche Stapel wird bei 20 Karten gedeckelt

Ohne Deckel sitzt jemand nach drei Wochen Pause vor 52 fälligen Karten und macht es gar nicht. Sortiert wird nach Überfälligkeit; nie gelernte Karten kommen zuletzt, damit zuerst der Rückstand abgebaut wird.

E17 · Als „gelernt" gilt eine Karte ab Fach 4

Fach 4 heißt: Abstand über drei Tage, also mindestens dreimal in Folge gekonnt. Das ist die Schwelle, ab der die Karte im Wochenfortschritt als erledigt zählt. Wer sie einmal richtig hatte, hat sie noch nicht gelernt.

E18 · Anhänge: Pfad aus UUID, Auslieferung nur über eine geprüfte Route

Drei Regeln, die zusammengehören:

  1. Der Speicherpfad ist <jahr>/<uuid><endung>. Der übermittelte Dateiname landet nur als Anzeigename in der Datenbank und wird nie zum Bilden eines Pfades benutzt. speicherpfad() wirft, wenn die Kennung keine UUID ist.
  2. Ausgeliefert wird ausschließlich über /anhaenge/<kennung>. Es gibt keinen Weg, über die Adresse eine beliebige Datei anzufordern, und die Berechtigung wird bei jedem Abruf neu geprüft – nicht nur beim Hochladen.
  3. Der Antwortkopf trägt Content-Security-Policy: sandbox und X-Content-Type-Options: nosniff. Anhänge sind Daten, kein Programm.

SVG ist trotz image/ nicht erlaubt: Es ist XML und kann Skripte enthalten.

E19 · Anhänge hängen an Aufgabe und Azubi

Der Bezug lautet <aufgabeId>::<azubiId>. Beim ersten Entwurf hing er nur an der Aufgabe – bei zwei Azubis hätte der zweite die Fotos des ersten gesehen.

E20 · Kriterien werden über ihre Position gespeichert

kriterien_erfuellt ist eine JSON-Liste der Positionen in der Aufgabendatei, etwa [0,2,3]. Wird die Reihenfolge der Kriterien nachträglich geändert, verschiebt sich die Zuordnung. Neue Kriterien gehören deshalb ans Ende – dieselbe Regel wie bei den Merkkarten (siehe E11).

E21 · Fehlende Berechtigung ist ein 404, kein 403

Seiten rufen erfordereSichtAufAzubi() auf, das mit „nicht gefunden" endet. Ein 403 würde bestätigen, dass es die angefragten Daten gibt. Serveraktionen brechen weiterhin mit einem Fehler ab – dort wäre eine Weiterleitung sinnlos.

Nebeneffekt: Next.js schwärzt Fehlermeldungen im Produktionsbetrieb. Eine eigene „Kein Zugriff"-Seite ließe sich anhand des Fehlertextes gar nicht erkennen.

E22 · Der Deckel für Serveraktionen liegt über der eigenen Grenze

serverActions.bodySizeLimit steht auf 30 MB, die eigene Prüfung auf 25 MB. Der Abstand ist Absicht: Eine zu große Datei soll die eigene, verständliche Meldung bekommen und nicht den Rumpfgrößenfehler des Gerüsts, der nur im Serverprotokoll auftaucht. Zusätzlich prüft der Browser vorab, damit am Mobilfunk nicht erst 30 MB übertragen und dann abgelehnt werden.

E23 · PDF mit Standardschriften statt eingebetteter Schrift

Die PDF-Ausgabe benutzt Helvetica, also eine der Schriften, die jeder PDF-Betrachter mitbringt. Das spart eine eingebettete Schriftdatei im Repository und hält die Dateien klein (ein Checklistenblatt wiegt rund 7 KB).

Der Preis ist der Zeichenvorrat WinAnsi. Enthalten sind alle deutschen Umlaute, ß, €, § und °. Nicht enthalten sind Zeichen wie ✓, ○, ▲ oder Emoji. Deshalb:

  • fuerPdf() ersetzt typografische Feinheiten (Anführungszeichen, Auslassungspunkte, geschützte Leerzeichen) und wirft nicht Darstellbares weg.
  • Ankreuzkästchen und Haken werden gezeichnet, nicht gesetzt. Das sieht in jedem Betrachter gleich aus und ist unabhängig von der Schrift.

Sollte später ein Zeichen außerhalb von WinAnsi wirklich gebraucht werden, ist der Weg das Einbetten einer freien Schrift – nicht das Aufweichen dieser Regel.

E24 · Fußzeilen brauchen einen aufgehobenen unteren Rand

PDFKit legt eine neue Seite an, sobald Text unterhalb des Satzspiegels geschrieben wird. Eine Fußzeile steht dort per Definition. Ohne Gegenmaßnahme entsteht je Seite und je Textaufruf eine leere Folgeseite.

fusszeilen() setzt deshalb page.margins.bottom kurzzeitig auf 0 und stellt den Wert danach wieder her. Ein Test zählt die Seitenobjekte im erzeugten PDF – damit fällt ein Rückfall sofort auf.

E25 · Ein Durchlauf kopiert die Texte seiner Vorlage

checkliste_lauf.titel und checkliste_schritt.text werden beim Start aus der Vorlage übernommen, statt zur Anzeigezeit nachgeschlagen zu werden.

Grund: Ein abgeschlossener Durchlauf ist ein Nachweis. Er muss auch in zwei Jahren noch dasselbe aussagen – auch wenn die Vorlage unter content/ inzwischen umformuliert, erweitert oder gelöscht wurde. Die Seite zeigt einen Hinweis, wenn es die Vorlage nicht mehr gibt, bleibt aber vollständig lesbar.

E26 · Mitlesen und Bearbeiten sind getrennte Rechte

Beim Checklisten-Durchlauf fallen die beiden Rechte auseinander:

  • Lesen darf der Eigentümer und sein Ausbilder (laufZumLesen).
  • Bearbeiten darf nur der Eigentümer (laufZumBearbeiten).

Der Ausbilder hakt also nichts für seinen Azubi ab. Im Nachweis soll keine Arbeit stehen, die der Azubi nicht selbst gemacht hat. Für die Abnahme gibt es die Praxisaufgaben – dort ist es genau andersherum.

E27 · Die Berichtsheftprüfung warnt, sie sperrt nicht

pruefeWochenblatt() liefert eine Liste von Warnungen und nichts anderes. Es gibt bewusst kein darfEinreichen().

Grund: Eine Anwendung, die das Einreichen verhindert, erzieht dazu, Unsinn einzutragen, bis die Prüfung zufrieden ist. Der Azubi soll die Warnung sehen – was er damit macht, entscheidet er. Der Knopf heißt dann eben „Trotz 3 Warnungen einreichen".

Die Prüfung läuft im Browser beim Tippen mit und benutzt dieselbe Funktion wie der Server. Sie ist keine Sicherheitsmaßnahme, sondern eine Hilfe.

E28 · Floskeln werden am Zusammenhang erkannt, nicht am Wort

„Diverse Arbeiten" ist eine Floskel. „Diverse Patchkabel konfektioniert, gemessen und im Verteiler beschriftet" ist keine. Ein reiner Wortabgleich würde beide gleich behandeln und wäre nach dem dritten Fehlalarm nur noch lästig.

Die Prüfung schlägt deshalb nur an, wenn

  • die Floskel den Eintrag im Wesentlichen ausmacht (höchstens 20 Zeichen Rest), oder
  • im ganzen Eintrag kein einziges Tätigkeitsverb vorkommt.

Dieselbe Verbliste trägt auch die Formulierungshilfe.

E29 · Die laufende Nummer richtet sich nach dem Zeitraum

Wird eine vergessene Woche nachgetragen, bekommt sie die Nummer, die ihr zeitlich zusteht; alle späteren Blätter rücken eine Nummer weiter.

Der Grund ist das Papierformular: Ein Ausbildungsnachweis wird chronologisch abgeheftet, und Nr. 12 zwischen Nr. 3 und Nr. 4 wäre gegenüber der IHK nicht erklärbar.

E30 · Kundennamen werden an der Rechtsform erkannt

Personennamen lassen sich nicht zuverlässig erkennen, ohne ständig Fehlalarme zu erzeugen. Firmennamen dagegen schon: Sie enden fast immer auf GmbH, AG, KG, mbH, e. V. und Ähnliches.

Das Muster schließt bewusst mit einem negativen Vorausblick statt mit \b – Rechtsformen wie „e. V." enden auf einen Punkt, und dort gibt es vor einem Leerzeichen keine Wortgrenze. Genau dieser Fall war zuerst durchgerutscht.

E31 · Die Formulierungshilfe steht im Quelltext, nicht unter content/

Tätigkeitsverben und die Gegenüberstellungen sind Bedienhilfe der Anwendung, kein Ausbildungsinhalt. Sie ändern sich nicht mit dem Lehrgang und gehören deshalb nicht in dieselbe Ablage wie Handouts und Aufgaben.

E32 · Die Musterlösung wird entfernt, nicht versteckt

Der Auftrag verlangt: Musterlösungen dürfen dem Azubi erst nach dem Absenden angezeigt werden und niemals vorher über die Schnittstelle abrufbar sein.

Umgesetzt ist das in src/lib/kontrollen/sicht.ts. sichtbareKontrolle() baut die Fassung, die den Browser erreichen darf; ist die Lösung nicht freigegeben, steht dort null. Es gibt keine Stelle, an der die vollständige Kontrolle an die Oberfläche gereicht und dort ausgeblendet wird.

Zwei Feinheiten:

  • Die Funktion nimmt den ganzen Betrachter und den Bearbeitungsstand entgegen, nicht ein vorberechnetes darfSehen. Ein Aufrufer kann also nicht aus Versehen true übergeben.
  • Die Zwischenspeicher-Aktion gibt nur einen Zeitstempel zurück. Sie reicht bewusst keinen Datensatz durch, in dem die Lösung mitreisen könnte.

Geprüft wird das doppelt: 21 Tests über alle Betrachter und Zustände, und ein Durchstich, der den Wortlaut aus der Inhaltsdatei im rohen Seitenquelltext sucht.

E33 · Notenschlüssel der IHK, und ein vorläufiges Ergebnis bleibt vorläufig

Gerechnet wird mit dem 100-Punkte-Schlüssel der IHK (ab 92 / 81 / 67 / 50 / 30 Prozent). Wer sich daran gewöhnt, erlebt in der Abschlussprüfung keine Überraschung.

Solange eine Frage ohne Punkte ist, gilt die Kontrolle nicht als korrigiert: korrigiert_am bleibt leer, das Ergebnis heißt „vorläufig", und die Kontrolle bleibt in der Liste „Was auf dich wartet". Ein halb korrigierter Fragebogen, der als fertig erscheint, wäre schlimmer als gar keine Anzeige.

E34 · Antworten werden beim Tippen gespeichert, je Frage einzeln

Eine Lernzielkontrolle dauert 45 Minuten. Ein Browserabsturz oder ein versehentlich geschlossener Reiter darf nicht 45 Minuten kosten.

Gespeichert wird je Frage einzeln mit knapp einer Sekunde Verzögerung – nicht das ganze Formular auf einmal. So bleibt jeder Schreibvorgang klein, und eine lange Antwort auf Frage 1 wird nicht bei jedem Tastendruck in Frage 6 neu geschrieben.

E35 · Wann eine Berufsbildposition als vermittelt gilt

Die Regel steht an genau einer Stelle: stufeAus() in src/lib/auswertung/abdeckung.ts.

Vermittelt ist eine Position, wenn es dazu gibt:

  • mindestens einen belastbaren Nachweis – eine abgenommene Praxisaufgabe oder einen abgeschlossenen Checklisten-Durchlauf –, oder
  • mindestens zwei stützende Nachweise, also gelesene Handouts.

Die Unterscheidung ist der Kern: Bei einer abgenommenen Aufgabe hat jemand etwas getan und ein anderer hat es gesehen. Ein gelesenes Handout sagt nur, dass der Stoff behandelt wurde. Ein einzelnes genügt deshalb nicht – sonst wäre die Abdeckung nach dem ersten Klick voll und die Ansicht wertlos.

Vier Lagen statt zwei:

Lage Bedeutung
vermittelt die Regel oben ist erfüllt
angefangen etwas erledigt, aber noch nicht genug
geplant Inhalt vorhanden, nichts davon erledigt
ohne Inhalt es gibt im ganzen Lehrgang nichts dazu

Die vierte Lage ist die wichtigste. Sie ist keine Lücke beim Azubi, sondern eine im Lehrgang, und wird deshalb getrennt und rot ausgewiesen. Dort hilft kein Lernen, sondern nur ein neues Handout oder eine neue Aufgabe unter content/.

Damit die Bewertung nachvollziehbar bleibt, führt die Ansicht je Position die einzelnen Nachweise mit Kürzel und Zustand auf – und die amtliche Fundstelle in der Verordnung.

E36 · Das Feedbackgespräch kopiert seine Vorlage

feedbackgespraech.inhalt enthält die verwendeten Kriterien und die Skala, nicht nur die Bewertungen.

Grund: Ein bestätigtes Gespräch gehört in die Ausbildungsakte. Ändert der Ausbilder später content/feedbackgespraech.yaml, bleibt ein geführtes Gespräch genau so, wie es geführt wurde. Dieselbe Überlegung wie bei den Checklisten-Durchläufen (siehe E25).

E37 · Beide Seiten bewerten getrennt

Ausbilder und Azubi tragen je eine eigene Spalte ein. Die Serveraktion schreibt immer nur die Spalte dessen, der gerade angemeldet ist – eine Selbsteinschätzung, die der Ausbilder eintragen könnte, wäre keine.

Liegen beide um zwei Stufen oder mehr auseinander, wird das Kriterium hervorgehoben. Das ist der eigentliche Wert des Bogens: nicht die Zahlen, sondern die Stellen, an denen sie auseinandergehen.

Bestätigen können beide einzeln; erst wenn beide bestätigt haben, ist das Gespräch unveränderlich. Solange nur eine Seite bestätigt hat, lässt sich die Bestätigung zurücknehmen.

E38 · Was offline liegen darf – und was niemals

Die Regel steht in public/sw.js, in einer einzigen Funktion: darfOfflineLiegen().

Offline liegt nur Lernmaterial: Handouts, Merkkarten, Glossar, die eigene Woche. Nicht Lernzielkontrollen, Berichtsheft, Bewertungen, Feedbackgespräche, Anhänge und kein PDF.

Der Grund ist keine Sparsamkeit, sondern eine Zusage aus dem Pflichtenheft: Musterlösungen und Bewertungsdaten stehen hinter einer eigenen Prüfung. Diese Prüfung findet auf dem Server statt. Was im Browserspeicher liegt, wird beim nächsten Zugriff nicht mehr geprüft – es wird ausgeliefert. Ein Dienstarbeiter, der „alles" zwischenspeichert, hebelt damit genau die Zusage aus, die am schwersten wiegt.

Seiten werden nach dem Muster erst das Netz, dann der Speicher geholt: Nach einer Aktualisierung sieht man sofort die neue Fassung, und nur bei fehlendem Netz die alte. Dateien unter /_next/static/ tragen eine Prüfsumme im Namen und werden zuerst aus dem Speicher bedient.

Beim Abmelden wird alles Persönliche weggeräumt – ein Werkstattgerät wird geteilt.

E39 · Die Warteschlange gehört zur Anmeldung, nicht zum Gerät

Offline entstandene Bewertungen liegen unter lernbar-warteschlange-<benutzerId> im localStorage.

Unter einem festen Schlüssel bekäme auf einem geteilten Gerät der Zweite die offenen Bewertungen des Ersten auf sein Konto geschrieben – und der Erste hätte sie nie. Beim Abmelden wird die Schlange deshalb auch nicht gelöscht: Sie gehört zur Anmeldung und wird beim nächsten Mal nachgereicht. Verworfen würde sonst eine Lernleistung, die jemand erbracht hat.

localStorage und nicht IndexedDB: Es geht um eine Handvoll Einträge zwischen zwei Verbindungen. Geht der Speicher verloren, ist eine Karte falsch terminiert – dafür lohnt kein zweites Datenmodell.

E40 · Der Tag einer nachgereichten Bewertung

tagDerBewertung() in src/lib/offline/nachtrag.ts, mit eigenen Tests.

Der Tag kommt vom Gerät, weil nur dort bekannt ist, wann tatsächlich gelernt wurde: Wer freitags im Keller lernt und sich montags wieder verbindet, soll seinen Freitag angerechnet bekommen.

Er wird auf heute gedeckelt. Sonst ließe sich das Wiederholen wegstellen, indem man die Uhr des Geräts vordatiert – die Karte käme dann erst in ferner Zukunft wieder. Unlesbares gilt als heute.

E41 · Im Druck gilt immer die helle Fassung

Die vollständige helle Palette wird unter @media print noch einmal gesetzt, und zwar auch für :root[data-thema="dunkel"].

Der Grund ist banal und war ein echter Fehler: Wer die dunkle Fassung eingestellt hatte, druckte schwarze Flächen. Eine einzelne Regel für body genügt nicht, weil die Karten, Ränder und Marken ihre Farben aus Merkmalen beziehen.

E42 · Bremse gegen Passwortraten im Speicher, je E-Mail-Adresse

Nach fünf Fehlversuchen wird eine Adresse gesperrt, erst eine Minute, dann jeweils doppelt so lang, höchstens 15 Minuten. Eine Stunde ohne Fehlversuch oder eine erfolgreiche Anmeldung setzt den Zähler zurück.

  • Je Adresse, nicht je Konto. Auch eine Adresse, die es nicht gibt, wird gesperrt. Sonst verriete die Sperre, welche Konten existieren (E6).
  • Nicht je IP-Adresse. Hinter dem Reverse Proxy käme die Adresse aus X-Forwarded-For, und darauf ist nur Verlass, wenn der Proxy richtig eingestellt ist. Bei einer Handvoll Konten lohnt das Durchprobieren vieler Konten mit wenigen Passwörtern ohnehin nicht.
  • Obergrenze 15 Minuten. Wer die Adresse des Ausbilders kennt, soll ihn nicht für Stunden aussperren können.
  • Im Speicher, nicht in der Datenbank. Es läuft genau ein Prozess. Ein Neustart setzt die Zähler zurück – das kann ein Angreifer nicht auslösen, der Betreiber aber schon, wenn er sich selbst ausgesperrt hat (docker compose restart lernbar).
  • Gezählt wird vor der Prüfung. Jeder Versuch gilt sofort als Fehlversuch und wird bei Erfolg zurückgenommen. Zwischen Sperrprüfung und Zählen liegt so kein await; sonst kämen gleichzeitige Anfragen alle an der Sperre vorbei.

E43 · Module schaltbar für die ganze Lern-Bar, gesperrt an drei Stellen, nie gelöscht

Merkkarten, Praxisaufgaben, Checklisten, Berichtsheft, Lernzielkontrollen und Feedbackgespräch lassen sich unter Verwaltung → Module abschalten. Wochenplan, Handouts, Glossar, Auswertung und Verwaltung bleiben immer an – an ihnen hängt alles andere, und die Auswertung passt sich ohnehin an.

  • Für die ganze Lern-Bar, nicht je Azubi. Ein Betrieb startet mit einer Fachrichtung und entscheidet einmal, womit er arbeitet. Schalter je Azubi hätten jede Ausbilderansicht je Person unterschiedlich gemacht. Ließe sich später ergänzen, ohne das Bestehende umzubauen: Der Stand ist schon heute ein Objekt, das an einer Stelle entsteht (repos/module).
  • In der Datenbank, nicht in einer Konfigurationsdatei. Ein Ausbilder soll ohne Zugang zum Server umschalten können, und die Änderung soll sofort wirken, ohne Neustart. Gespeichert wird nur, was je umgeschaltet wurde; fehlt die Zeile, ist das Modul an. Eine bestehende Datenbank verhält sich nach der Migration also genau wie vorher.
  • Gesperrt an drei Stellen, nicht im Layout. Seiten rufen erfordereModul auf (Antwort „nicht gefunden", wie bei fehlender Berechtigung, E21), Routen für PDF, Anhänge und Lernstand fragen modulAn, Serveraktionen rufen pruefeModul. Ein Layout je Modul wäre kürzer gewesen, greift aber nicht zuverlässig: Next.js rendert ein Layout beim Wechsel zwischen den Seiten darunter nicht neu.
  • Abschalten löscht nichts. Beim Berichtsheft ist das zwingend – es ist ein Nachweis. Für alle anderen Module gilt dasselbe, damit niemand beim Ausprobieren Daten verliert.
  • Abgeschaltet heißt: trägt nirgends bei. Kein Eintrag im Wochenplan, kein Anteil am Wochenfortschritt, kein Nachweis in der Rahmenplanabdeckung, kein Vorschlag fürs Berichtsheft, kein Kurzbefehl im App-Symbol. Sonst stünden Inhalte für immer als offen da, die niemand mehr bearbeiten kann.
  • Offline gesammelte Bewertungen werden verworfen, wenn die Merkkarten aus sind. Die Lernstand-Schnittstelle antwortet mit 410 statt 404, und das Gerät leert daraufhin seine Warteschlange, statt sie bei jeder Verbindung erneut zu schicken.
  • Die Prüfung der Anmeldung kommt zuerst, die des Moduls danach. Sonst sähe ein nicht angemeldeter Besucher an „nicht gefunden" statt der Anmeldeseite, welche Module abgeschaltet sind.