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>
474 lines
21 KiB
Markdown
474 lines
21 KiB
Markdown
# 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.
|