- Taegliche Stichworte, je Tag ein eigenes Formular - Wochenblatt mit Betrieb, Berufsschule und Sonstiges, je mit Stunden - Vorschlaege aus Tagesnotizen, Aufgaben, Checklisten und Handouts - Pruefung mitlaufend beim Tippen: fehlende Stunden, Floskeln, IP- und MAC-Adressen, E-Mail, Zugangsdaten, Kundennamen - Warnung, keine Sperre - Einreichen ist immer moeglich - Formulierungshilfe mit Taetigkeitsverben und Gegenueberstellungen - Einreichen, zurueckgeben mit Anmerkung, abzeichnen; danach gesperrt - Fehlende Wochen werden benannt und lassen sich nachtragen; die laufende Nummer richtet sich nach dem Zeitraum, nicht nach dem Anlegen - PDF im IHK-Layout, einzeln oder als ganzer Zeitraum Behoben: Im PDF-Kopffeld liefen die Spalten auseinander (dokument.y als Bezugspunkt in der Schleife). Das Abzeichnen-Formular hatte keine action - gefunden ueber die ESLint-Meldung zur ungenutzten Serveraktion. 55 neue Tests, insgesamt 241. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
312 lines
14 KiB
Markdown
312 lines
14 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.
|