Ein Abschnitt oder ein einzelner Schritt erscheint nur, wenn seine Bedingung zutrifft — abhaengig von einer Variable des Laufs oder vom Zustand eines anderen Schritts. Der echte Fall steht im Seed: "Mitarbeiter-Onboarding" traegt beide Konto-Abschnitte, fuenf AD-Schritte und vier fuer Entra ID, und `konto_typ` sagt beim Start, welcher gilt. Bisher klickte ein Techniker die anderen neun bei jedem Onboarding weg. Entscheidungen, alle im Issue begruendet: - Laufend ausgewertet, nicht beim Start festgeschrieben. Fuer die Abteilungsabschnitte (#031) ist Festschreiben richtig; fuer Bedingungen nicht — die Antwort auf "ist Pruefen fehlgeschlagen?" entsteht erst waehrend der Arbeit. Ein struktureller Test prueft, dass alle drei Schichten (Abschlussregel, Abarbeitung, Druck) die Auswertung rufen. - Nicht entscheidbar heisst: gilt. Ein Lauf ohne konto_typ zeigt beide Abschnitte — genau wie vorher. Ein Abschnitt zu viel ist sichtbar und korrigierbar, ein fehlender faellt niemandem auf. - OFFEN ist eine Antwort, keine Luecke: waere es unentscheidbar, waere ein bedingter Abschnitt von Anfang an sichtbar. - Eine Bedingung, kein Ausdrucksbaum. Kein und, kein oder. - Verwiesen wird ueber Titel-Pfade, nicht ueber Kennungen: vier Wege schreiben eine Struktur neu und geben neue Kennungen. Zwei stille Datenverluste gefunden, der zweite aelter als dieser Meilenstein: - Der Import verwarf `nurFuerAbteilungen`. Der Export schreibt es seit #031, das Schema nimmt es an, der Import setzte es nicht — eine exportierte Vorlage verlor ihre Abteilungsabschnitte und galt dort fuer alle. - Die tiefe Kopie verwarf es ebenfalls, womit jedes "Bearbeiten" einer Vorlage alle Einschraenkungen verlor. Ursache war ein von Hand getippter Zwischentyp, der weniger Felder nannte als die Abfrage liefert; jetzt aus Prisma abgeleitet, beide Kopierwege teilen eine Stelle, und ein Test vergleicht Kopie und Original Feld fuer Feld. Gefunden hat beides die Klickprobe am gebauten Stand, nicht die Suite: im Editor stand "Bedingung gesetzt", nach "Bearbeiten" war sie weg. Dreizehn Gegenproben liefen, alle wurden rot — und der teuerste Befund lag ausserhalb von allen. 1499 Vitest-Tests (vorher 1457), 115 E2E-Ablaeufe gruen. Closes #39 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
315 lines
19 KiB
Markdown
315 lines
19 KiB
Markdown
# Konventionen für Laufzettel
|
||
|
||
Diese Datei ist verbindlich. Wer hier etwas ändert, ändert es auch in `docs/architektur.md`.
|
||
|
||
## Sprache
|
||
|
||
**Deutsch, überall wo ein Mensch liest oder benennt** — auch Bezeichner. Funktions- und
|
||
Variablennamen, Prisma-Modelle und -Felder, Enum-Werte, Fehlerklassen, Dateinamen, Kommentare,
|
||
Testbeschreibungen, Commit-Betreffs und Dokumentation sind deutsch. **Umlaute werden
|
||
ASCII-gefaltet**: `Geraet`, `geaendertAm`, `laeufe`, `veroeffentlichtAm`.
|
||
|
||
**Gefaltet werden Bezeichner, nicht Fließtext.** Alles, was ein Mensch zu lesen bekommt, schreibt
|
||
sich richtig: Oberflächentexte, Fehlermeldungen, Protokoll-Aktionen (`"Rolle geändert"`),
|
||
Überschriften. „Kuerzel“ in einer Fehlermeldung ist ein Fehler, `kuerzel` als Feldname nicht.
|
||
|
||
Englisch bleiben nur:
|
||
|
||
- vom Framework vorgegebene Namen: `GET`, `POST`, `layout.tsx`, `route.ts`, `page.tsx`, `proxy.ts`,
|
||
`dependencies`, `default`
|
||
- Lehnwörter aus dem Fachjargon: `tagCode`, `Token`, `OIDC`, `LDAP`, `Hostname`, `Shell`,
|
||
`Markdown`, `Nonce`, `Healthcheck`
|
||
|
||
Kommentare erklären **warum**, nicht was, und tragen wo hilfreich die Issue-Nummer: `(#007)`.
|
||
|
||
## Namensabbildung zur Auftragsvorgabe
|
||
|
||
Der Auftrag nennt englische Modelle. Verbindlich gilt:
|
||
|
||
| Vorgabe | Modell | Vorgabe | Modell |
|
||
|---|---|---|---|
|
||
| User | `Benutzer` | Template | `Vorlage` |
|
||
| Customer | `Kunde` | TemplateVersion | `VorlagenVersion` |
|
||
| Setting | `Einstellung` | TemplateVariable | `VorlagenVariable` |
|
||
| DirectorySource | `Verzeichnisquelle` | TemplateSection | `VorlagenAbschnitt` |
|
||
| Employee | `Mitarbeiter` | TemplateStep | `VorlagenSchritt` |
|
||
| Department | `Abteilung` | ChecklistRun | `Lauf` |
|
||
| Device | `Geraet` | StepResult | `Schrittergebnis` |
|
||
| DeviceTemplateDefault | `GeraetetypVorlage` | Comment | `Kommentar` |
|
||
| — (ergänzt) | `AbgleichSperre` | AuditLog | `Protokoll` |
|
||
| — (ergänzt) | `BenutzerKunde` | — (ergänzt) | `TagCodeVergabe` |
|
||
| — (ergänzt) | `Zustellung` | | |
|
||
|
||
Enums: `Rolle {ADMIN REDAKTEUR TECHNIKER LESER MITARBEITER}` · `Betriebsmodus {INTERN DIENSTLEISTUNG}` ·
|
||
`LaufStatus {OFFEN IN_ARBEIT ABGESCHLOSSEN ARCHIVIERT}` ·
|
||
`SchrittZustand {OFFEN ERLEDIGT NICHT_ZUTREFFEND FEHLGESCHLAGEN}` ·
|
||
`SchrittTyp {HAKEN TEXT ZAHL AUSWAHL DATUM BEFEHL}` (später `AKTION`) ·
|
||
`VariablenTyp {TEXT ZAHL AUSWAHL DATUM}` ·
|
||
`GeraeteTyp {PC NOTEBOOK SERVER FIREWALL SWITCH DRUCKER RAUM SONSTIGES}` ·
|
||
`VersionStatus {ENTWURF VEROEFFENTLICHT}` · `Herkunft {ABGLEICH MANUELL}` ·
|
||
`QuellenArt {LDAP GRAPH SCIM}` ·
|
||
`Zustellweg {MAIL WEBHOOK}` · `Zustellzustand {OFFEN GESENDET FEHLGESCHLAGEN AUFGEGEBEN}` ·
|
||
`Meldeanlass {LAUF_ZUGEWIESEN LAUF_UEBERFAELLIG LAUF_ABGESCHLOSSEN ABGLEICH_FEHLGESCHLAGEN}`.
|
||
|
||
`SEED_MODE` akzeptiert `DIENSTLEISTUNG` und `SERVICE` als Alias.
|
||
|
||
## Schichtung
|
||
|
||
```
|
||
src/server/services/* Regeln und Schreiboperationen. Werfen eigene …Fehler-Klassen.
|
||
src/server/queries/* Lesemodelle. Wenden Rollen- und Mandantensicht selbst an.
|
||
src/server/actions/* Dünne "use server"-Einstiege. Prüfen genau ein Recht, rufen den Service.
|
||
src/lib/* Framework-freie Helfer. Kein Next, kein React, gut testbar.
|
||
src/app/api/* Route Handler. Nur Zustandsstrom, QR-SVG, CSV, Anhaenge, Auth,
|
||
Healthcheck. Minimal.
|
||
src/components/* Darstellung. Keine Geschäftslogik.
|
||
```
|
||
|
||
**Keine Geschäftslogik in Komponenten oder Route Handlern.** Eine Server Action ist ein
|
||
Dreizeiler: Recht prüfen, Zod-Schema anwenden, Service rufen.
|
||
|
||
## Feste Regeln, die in Tests festgehalten sind
|
||
|
||
- **Versionierung:** Eine veröffentlichte `VorlagenVersion` wird nie verändert. „Bearbeiten“
|
||
erzeugt eine tiefe Kopie als Entwurf. Höchstens ein Entwurf je Vorlage. Läufe verweisen nur auf
|
||
veröffentlichte Versionen.
|
||
- **Archiv:** `laufMussVeraenderbarSein(lauf)` sitzt an genau einer Stelle und wird vor jeder
|
||
Änderung an Kopfdaten, `Schrittergebnis` und `Kommentar` gerufen. Zurückholen nur `ADMIN`.
|
||
- **Abschluss:** nur wenn alle Pflichtschritte `ERLEDIGT` oder `NICHT_ZUTREFFEND` sind.
|
||
`NICHT_ZUTREFFEND` und `FEHLGESCHLAGEN` verlangen eine Notiz.
|
||
- **Mandant:** Jede kundenbezogene Zeile trägt `kundeId`. `kundeErmitteln()` liefert im Modus
|
||
`INTERN` immer die eigene Organisation und **verwirft** eine mitgeschickte `kundeId`.
|
||
- **Rechte:** nur serverseitig, aus der `RECHTE`-Tabelle über `darf()` / `rechtErforderlich()`.
|
||
**`lesen` hat nicht jede Rolle** — `MITARBEITER` darf es seit #035 nicht, und `selbstauskunft` ist
|
||
dessen Ersatz und kein Zusatz. Jeder Navigationseintrag nennt deshalb sein Recht; ein Eintrag
|
||
ohne `recht` ist die Ausnahme.
|
||
Rolle und Aktiv-Status werden pro Anfrage **aus der Datenbank** gelesen, nicht aus dem Token.
|
||
**Jede Seite prüft selbst** — die Hülle unter `src/app/(app)/layout.tsx` ist das zweite Netz,
|
||
nicht das erste: Next rendert Layouts bei Navigation innerhalb desselben Layouts nicht neu.
|
||
**Route Handler deckt die Hülle nicht ab**; alles unter `src/app/api/`, das mehr tut als Auth
|
||
und Healthcheck, ruft selbst `rechtErforderlich()`. Der Proxy prüft das Cookie absichtlich nur
|
||
auf Vorhandensein, nie inhaltlich.
|
||
Beides prüft `tests/zugang.test.ts` seit #036 **strukturell**: es liest jede `page.tsx` und jeden
|
||
`route.ts` und schlägt an, wenn eine Datei nichts prüft. Die Ausnahmen stehen dort namentlich,
|
||
samt Begründung — ein neuer Handler ohne Prüfung soll jemanden zwingen, sie aufzuschreiben.
|
||
Vorher hielt die Zusage nur durch Nachsehen von Hand, und das gilt für den Tag, an dem es lief.
|
||
- **Server-Module gehören nicht ins Browser-Bündel.** Eine Client-Komponente importiert nichts aus
|
||
`src/server/*`. Was beide Seiten brauchen (Beschriftungen, Formate), liegt in `src/lib` —
|
||
`ROLLEN_TEXT` etwa in `src/lib/rollen-text.ts`, während `RECHTE` und `darf()` serverseitig
|
||
bleiben.
|
||
- **`tagCode`:** wird nie geändert und nie wiederverwendet, auch nicht nach Archivierung.
|
||
- **Löschen:** Geräte, Abteilungen und Vorlagen werden archiviert. Mitarbeiter werden nur
|
||
`aktiv = false`. Nichts wird gelöscht, woran ein Lauf hängt. Genau **eine** Ausnahme gibt es: ein
|
||
Gerät ohne Läufe lässt sich endgültig löschen, weil der Vertipper von eben sonst für immer in der
|
||
Liste stünde. Das verlangt `verwaltung` — der Tag-Code ist danach für immer verbrannt, und der
|
||
gedruckte Aufkleber wertlos.
|
||
- **`{{generiertes_passwort}}`:** wird je Anzeige neu erzeugt und **nie** gespeichert — nicht in
|
||
`Schrittergebnis`, `Kommentar`, `Protokoll` oder der Druckansicht.
|
||
- **Bedingte Teile:** Eine Bedingung an `VorlagenAbschnitt` oder `VorlagenSchritt` sagt, wann der
|
||
Teil gilt (#039). **Nicht entscheidbar heißt: gilt** — ein Abschnitt zu viel ist sichtbar und
|
||
korrigierbar, ein fehlender fällt niemandem auf. Eine Bedingung, kein Ausdrucksbaum: kein `und`,
|
||
kein `oder`. Die Auswertung sitzt in `src/lib/bedingungen.ts` und wird von **drei** Schichten
|
||
gerufen (Abschlussregel, Abarbeitung, Druck); `geltendeStruktur` legt die Reihenfolge der zwei
|
||
Filter fest — erst die beim Start festgeschriebene Abteilungsauswahl aus #031, dann die laufende
|
||
Auswertung. `tests/bedingte-laeufe.test.ts` prüft strukturell, dass keine Schicht sie vergisst.
|
||
**Verwiesen wird über Titel-Pfade, nicht über Kennungen.** Austausch, Import, Seed und
|
||
`entwurfSpeichern` schreiben eine Struktur neu und geben neue Kennungen; ein Titel-Pfad überlebt
|
||
das. `bedingungAufloesen` ist für alle vier dieselbe Stelle.
|
||
- **Erweiterbarkeit:** Kein Codepfad nimmt an, dass ein Schritt nur von einem Menschen erledigt
|
||
wird. `SchrittTyp` bekommt später `AKTION`, `konfig` und `wert` sind Json, und
|
||
`Lauf.erstelltVonId` wie `Schrittergebnis.aktualisiertVonId` dürfen leer sein — ein Systemlauf
|
||
ist darstellbar.
|
||
- **Regeln stehen in der Datenbank, nicht nur im Service.** CHECK-Bedingungen, partielle
|
||
Eindeutigkeitsindizes, zusammengesetzte Fremdschlüssel und Trigger halten fest, was auch
|
||
dann gelten muss, wenn jemand an der Service-Schicht vorbei schreibt.
|
||
`tests/datenbankregeln.test.ts` zählt sie **namentlich** auf — dort steht die jeweils aktuelle
|
||
Liste, und genau deshalb steht hier keine Zahl mehr: sie lief dreimal der Wirklichkeit nach. Die Mandantengrenze ist
|
||
einer davon: ein Lauf kann strukturell kein Gerät, keine Person und keine Abteilung eines
|
||
fremden Kunden wählen. Der Service prüft dasselbe noch einmal, nur mit besserer Meldung.
|
||
|
||
## Werkzeuge und Versionen
|
||
|
||
Node 24 (`.nvmrc`), pnpm 12 über corepack. Exakt gepinnt, weil dort etwas im Fluss ist:
|
||
`next@16.3.6`, `react@19.3.0`, `prisma`/`@prisma/client`/`@prisma/adapter-pg` auf **`7.10.0`**
|
||
(der `latest`-Tag von `prisma` zeigt auf einen 8.0-RC, `@prisma/client` nicht — das würde
|
||
auseinanderlaufen), `vitest@5.0.1`, `@playwright/test@1.63.0`.
|
||
|
||
Zwei Obergrenzen, die nicht angehoben werden dürfen:
|
||
|
||
- **`eslint` bleibt auf 9.x.** `eslint-config-next` bringt `eslint-plugin-react@7.37.5` mit, das
|
||
`eslint` nur bis `^9.7` unterstützt; mit eslint 10 bricht das Regelwerk beim Laden ab. Eine
|
||
neuere Fassung des Plugins gibt es nicht.
|
||
- **`typescript` bleibt auf `~6.0.3`.** `typescript-eslint@8` erlaubt nur `>=4.8.4 <6.1.0`.
|
||
TypeScript 7 ist noch nicht unterstützt.
|
||
|
||
Build-Skripte von Abhängigkeiten sind in `pnpm-workspace.yaml` unter `allowBuilds` freigegeben
|
||
(pnpm 12 liest das `pnpm`-Feld in `package.json` nicht mehr).
|
||
|
||
**`minimumReleaseAgeExclude` bleibt leer.** pnpm 12 nimmt Pakete erst nach einer Wartezeit an — ein
|
||
Schutz gegen ein kompromittiertes Release, das Stunden später zurückgezogen wird. Trägt `pnpm add`
|
||
dort von sich aus eine Ausnahme ein, ist das kein Formfehler, sondern die Meldung „diese Version ist
|
||
frisch von heute“. Dann gehört **eine abgelagerte Version** gepinnt, nicht die Ausnahme committet.
|
||
`nodemailer` steht deshalb exakt auf `10.0.10` und nicht auf der neuesten Fassung (#034).
|
||
|
||
## Next 16
|
||
|
||
`middleware.ts` heißt **`proxy.ts`** und exportiert `proxy()`, Node-Laufzeit. `cookies()`,
|
||
`headers()`, `params` und `searchParams` sind **async**. Route Handler:
|
||
`(anfrage, { params }: { params: Promise<{ id: string }> })`. Turbopack ist Standard.
|
||
`next lint` ist entfernt, `pnpm lint` ruft `eslint` direkt.
|
||
|
||
## Prisma 7
|
||
|
||
Generator `prisma-client` mit Pflicht-`output` nach `src/generated/prisma`, `datasource` ohne
|
||
`url`, Zugriff über `@prisma/adapter-pg`.
|
||
|
||
**Importpfade, einmal richtig:** Modelltypen kommen aus `@/generated/prisma/client`
|
||
(`import type { Benutzer } from "@/generated/prisma/client"`), Enum-**Werte** aus
|
||
`@/generated/prisma/enums`. `@/generated/prisma/models` exportiert nur die Hilfstypen der Abfragen,
|
||
nicht die Modelle selbst — ein `import` von dort schlägt fehl. Ein Adapter ist **Pflicht** — `new PrismaClient()` ohne
|
||
`adapter` wirft. Seed steht in `prisma.config.ts` unter `migrations.seed`. Der Client wird über
|
||
den Lazy-Proxy in `src/lib/db.ts` geholt, damit `next build` ohne `DATABASE_URL` läuft.
|
||
|
||
## Tests brauchen die Testdatenbank für sich allein
|
||
|
||
`tests/helpers/db.ts` leert vor jedem Test alle Tabellen. Zwei gleichzeitige Läufe gegen dieselbe
|
||
Datenbank räumen sich deshalb gegenseitig die Daten weg, und das sieht aus wie ein echter Fehler
|
||
(typisch: `Foreign key constraint violated on Protokoll_benutzerId_fkey`). Wer parallel prüfen
|
||
lässt — etwa einen Review-Agenten —, gibt ihm eine **eigene** Datenbank:
|
||
|
||
```bash
|
||
createdb -h 127.0.0.1 -p 5434 -U laufzettel laufzettel_review
|
||
TEST_DATABASE_URL=postgresql://laufzettel:laufzettel@localhost:5434/laufzettel_review pnpm test
|
||
```
|
||
|
||
## Oberfläche prüfen
|
||
|
||
**Immer gegen den gebauten Stand, nie gegen `pnpm dev`:**
|
||
|
||
```bash
|
||
pnpm build && PORT=3300 APP_URL=http://127.0.0.1:3300 pnpm start
|
||
```
|
||
|
||
In dieser Umgebung hydriert `next dev` nicht: die Seiten werden serverseitig
|
||
gerendert und sehen vollständig richtig aus, aber React hängt sich nie an — kein
|
||
Knopf, kein Formular, kein Sortieren tut etwas. Nachgewiesen in #010, und zwar
|
||
auch ohne `src/proxy.ts` und ohne `output: "standalone"`; der HMR-WebSocket
|
||
scheitert dort mit `ERR_INVALID_HTTP_RESPONSE`. Es liegt also am Werkzeug, nicht
|
||
an diesem Code — der gebaute Stand hydriert einwandfrei (1281 von 1296 Elementen
|
||
tragen React-Knoten, im Entwicklungsmodus 2).
|
||
|
||
Das ist heimtückisch: ein Screenshot beweist damit **nichts** über Bedienbarkeit.
|
||
Wer eine Oberfläche abnimmt, klickt sie im gebauten Stand durch — hinzufügen,
|
||
speichern, neu laden, und nachsehen, ob es wirklich in der Datenbank steht.
|
||
`pnpm docs:screenshots` verlangt deshalb ebenfalls den gebauten Stand.
|
||
|
||
## Die Ende-zu-Ende-Abläufe laufen vor jedem Merge nach `main`
|
||
|
||
```bash
|
||
pnpm build && PORT=3300 APP_URL=http://127.0.0.1:3300 pnpm start &
|
||
E2E_APP_URL=http://127.0.0.1:3300 pnpm test:e2e
|
||
```
|
||
|
||
**Nicht im Commit-Tor, und das ist eine Rechnung, keine Bequemlichkeit.** Gemessen am 2026-10-11:
|
||
Bau 14 s, Start 7 s, Abläufe **256 s** — zusammen 4 min 37 s, zu den 4 min des Commit-Tors. Neun
|
||
Minuten vor jedem Commit ist ein Tor, das umgangen wird, und ein umgangenes Tor prüft nichts.
|
||
|
||
**Vor jedem Merge ist die Regel, weil sie gereicht hätte.** In Phase 2 waren das acht Läufe. Beim
|
||
ersten vollständigen Durchgang nach Monaten waren **sieben Abläufe rot** (#027): ein Test, der die
|
||
Felder des Laufzustands aufzählt und `anzahlAnhaenge` nicht kannte (seit #033); ein Selektor, der
|
||
seit #020 zweimal traf; eine feste Zugweite von 300 px bei 532 px Abstand (seit #030/#031); und
|
||
Entwicklungsdaten, die vom Seed abwichen. Keiner davon war ein Produktfehler — und jeder wäre am Tag
|
||
seiner Entstehung aufgefallen.
|
||
|
||
**Diese Zusagen stehen ausschließlich dort**, und keine davon lässt sich nach Vitest holen: sie
|
||
brauchen einen echten Browser.
|
||
|
||
| Zusage | Warum nur im Browser |
|
||
|---|---|
|
||
| Kein horizontales Scrollen bei 390 px | verlangt Layout, nicht Logik |
|
||
| Scannen über `BarcodeDetector` | Browser-API samt Polyfill |
|
||
| Sortieren per Finger | Touch-Ereignisse und `dnd-kit` |
|
||
| Zwei Sitzungen sehen einander live | zwei Browser-Kontexte gleichzeitig |
|
||
| Rückfall auf Polling ohne `EventSource` | das Client-Verhalten; der Handler-Teil steht in Vitest |
|
||
| „Alle Befehle kopieren“ zeichengenau | Zwischenablage |
|
||
| Druckansicht und Etiketten | Print-CSS |
|
||
| Eine Rolle sieht keine Schreib-Knöpfe | gerendertes DOM |
|
||
| Dass überhaupt hydriert wird | der Grund für „gebauter Stand“, siehe oben |
|
||
|
||
**Was sich holen ließ, ist geholt.** Die Rechteprüfung der Route Handler steht seit #036 in
|
||
`tests/anhang-route.test.ts`, `tests/lauf-ereignisse.test.ts` und `tests/scim.test.ts` — die Handler
|
||
werden mit echten `Request`-Objekten gefahren. Dazu prüft `tests/zugang.test.ts` **strukturell**,
|
||
dass jede Seite und jeder Route Handler sein Recht selbst prüft; die Ausnahmen (Auth und
|
||
Healthcheck) stehen dort namentlich. Vorher hielt diese Zusage nur durch Nachsehen von Hand.
|
||
|
||
Die 35 übersprungenen Abläufe sind die modusabhängigen: sie laufen im jeweils anderen
|
||
`Betriebsmodus` und sagen das beim Überspringen.
|
||
|
||
**Die Abläufe räumen auf, was sie anlegen** (#027). Alles trägt `ZZ` am Anfang;
|
||
`tests/e2e/aufraeumen.ts` greift daran, **vor und nach** dem Lauf. Der Nachlauf fängt Testfehler und
|
||
Zeitabläufe, der Vorlauf fängt `Strg+C` — ein Vorlauf lässt sich nicht überspringen. Er sagt laut,
|
||
wenn er einen Rückstand findet.
|
||
|
||
## Vor jedem Commit
|
||
|
||
```bash
|
||
docker compose -f docker-compose.test.yml up -d --wait ldap mailpit
|
||
pnpm lint && pnpm typecheck && pnpm format:pruefen && pnpm test && pnpm pruefe:doku
|
||
```
|
||
|
||
**Die beiden Container gehören in die erste Zeile.** Ohne sie überspringt die Suite Tests, sagt das
|
||
auf stderr — und sieht trotzdem grün aus. Wer nur diesem Abschnitt folgte, hätte das Betroffene nie
|
||
geprüft. In CI bricht sie stattdessen ab, absichtlich.
|
||
|
||
| Container | Fehlt er, fehlen | Betroffen |
|
||
|---|---|---|
|
||
| `ldap` | **22 Tests** (nachgezählt: 81 statt 59) | der ganze Verzeichnis-Abgleich, `tests/abgleich.test.ts` und `tests/verzeichnis.test.ts` |
|
||
| `mailpit` | **3 Tests** (nachgezählt: 36 statt 33) | der echte SMTP-Versand in `tests/benachrichtigungen.test.ts` — die vier Anlässe, die Passwort-Prüfung über alle Rumpftexte und „nichts zweimal“ |
|
||
|
||
`ldap` fiel bei der Abnahme zu #019 auf, `mailpit` kam mit #034 dazu. Beide Zahlen sind **gemessen**,
|
||
nicht geschätzt — in #026 hätte beinahe eine geratene in dieser Datei gestanden.
|
||
|
||
`format:pruefen` gehört dazu, seit der Review in #010 darauf gestoßen ist: `lint-staged` formatiert
|
||
beim Commit automatisch nach, und das Ergebnis wäre ein Commit mit Änderungen, die niemand gesehen
|
||
hat. Lieber vorher sehen, was sich ändert.
|
||
|
||
Der Pre-Commit-Hook erzwingt Lint und Format, der Commit-Msg-Hook Conventional Commits mit
|
||
höchstens 72 Zeichen im Betreff. Ein Commit pro abgeschlossener, lauffähiger Änderung. Kein
|
||
`wip` auf `main`. Fußzeile referenziert das Issue: `Closes #12`.
|
||
|
||
## Gestaltung
|
||
|
||
Dunkel zuerst, ein einziger Akzent (Signal-Lime `#C6F135`) nur für Aktionen, Fortschritt, Fokus
|
||
und aktive Zustände. Token stehen als CSS-Variablen in `src/app/globals.css` unter `@theme`;
|
||
Komponenten greifen nur darauf zu, nie auf rohe Hexwerte. Schriften selbst gehostet über
|
||
`@fontsource-variable`. Icons ausschließlich Lucide-Stroke-SVGs — **keine Emojis**. Keine
|
||
Verläufe, keine Schatten; Karten mit 1-px-Linie. Buttons als Pillen, 40 px hoch, am Handy 44–46 px.
|
||
Abschnitte mit zweistelliger Nummer (`01 · Vorbereitung`), die aus der Position
|
||
kommt und nie gespeichert wird. Druckansichten und Etiketten immer hell.
|
||
**Am Handy nie horizontales Scrollen** — bei 390 px geprüft.
|
||
|
||
**„Nie auf rohe Hexwerte" meint die Oberfläche.** Es gibt genau sechs begründete Ausnahmen, und sie
|
||
haben alle denselben Grund: dort entscheidet nicht unser Farbschema, sondern etwas außerhalb davon.
|
||
|
||
| Stelle | Wert | Warum kein Token |
|
||
|---|---|---|
|
||
| `src/app/drucken/lauf/[id]/page.tsx`, `src/app/drucken/etiketten/page.tsx` | `#ffffff` | Druck ist immer hell, unabhängig vom Erscheinungsbild der Anwendung |
|
||
| `src/lib/qr.ts` | `#000000ff` / `#ffffffff` | Ein QR-Code muss reines Schwarz auf reinem Weiß sein, sonst liest ihn kein Decoder |
|
||
| `src/app/layout.tsx` (`themeColor`), `src/app/manifest.ts` (zweimal) | `#0B0D10` | `themeColor` und das Web-App-Manifest vertragen keine CSS-Variablen |
|
||
|
||
Wer eine siebte hinzufügt, trägt sie hier ein — oder lässt es.
|
||
|
||
> Der folgende Block ist **nicht von uns**: `next dev` schreibt ihn bei jedem Start selbst in diese
|
||
> Datei und stellt ihn wieder her, wenn man ihn löscht. Er steht deshalb hier, auf Englisch, und
|
||
> wird mitcommittet, damit der Arbeitsbaum sauber bleibt.
|
||
|
||
<!-- BEGIN:nextjs-agent-rules -->
|
||
|
||
# This is NOT the Next.js you know
|
||
|
||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
||
|
||
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||
|
||
<!-- END:nextjs-agent-rules -->
|