Files
lernbar/ENTSCHEIDUNGEN.md
T
Florian WildeandClaude Opus 5 c61d5711e0 Baustein 10b: Anleitung mit Screenshots, Abnahmelauf, Fassung 1.0.0
docs/ANLEITUNG.md: 20 Abschnitte, 27 Bildschirmfotos, getrennt fuer
Ausbilder und Azubi, dazu "Wenn etwas nicht geht". Die Bilder entstehen
automatisiert ueber npm run screenshots - was dort steht, ist gemessen
und nicht gemalt.

scripts/pruefungen/abnahme.mjs (npm run abnahme) laeuft gegen eine
frische, leere Datenbank in einem Wegwerfverzeichnis: aufsetzen, Konten
anlegen, arbeiten, abnehmen, sichern, beschaedigen, zurueckspielen. Eine
Sicherung, die nie zurueckgespielt wurde, ist keine Sicherung. 33 von 33
Pruefungen erfuellt, Protokoll in docs/ABNAHME.md.

Dabei drei echte Fehler gefunden, alle in Wegen, die nie ausgefuehrt
worden waren:

- Die Sicherungsskripte liefen nicht in Git Bash, obwohl das in ihrem
  Kopf stand: tar deutet den Doppelpunkt in "C:\..." als Rechnernamen.
  Beide wechseln jetzt erst ins Verzeichnis und uebergeben einen blossen
  Dateinamen.
- Auf einer frischen Datenbank gibt es noch kein Wochenblatt; der Weg
  jedes neuen Azubis war nie durchlaufen worden.
- Der Abnahmelauf selbst liess unter Windows seinen Server ueberleben
  (kill() beendet nur die cmd.exe) und prueft deshalb jetzt vorher, ob
  der Port frei ist - sonst haette er gegen fremde Daten geprueft.

Fassung 1.0.0: package.json stand noch auf 0.1.0, waehrend das
Aenderungsprotokoll bei 0.9.0 war.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 19:26:34 +02:00

474 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
---
<!-- Weitere Entscheidungen werden unten angefügt und fortlaufend nummeriert. -->
## 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.
## E12 · Glossarverweise: Link statt Überfahren
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.