# 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`. 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` | Enums: `Rolle {ADMIN REDAKTEUR TECHNIKER LESER}` · `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`) · `GeraeteTyp {PC NOTEBOOK SERVER FIREWALL SWITCH DRUCKER RAUM SONSTIGES}` · `VersionStatus {ENTWURF VEROEFFENTLICHT}` · `Herkunft {ABGLEICH MANUELL}`. `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 Polling, QR-SVG, CSV, 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()`. Rolle und Aktiv-Status werden pro Anfrage **aus der Datenbank** gelesen, nicht aus dem Token. - **`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. - **`{{generiertes_passwort}}`:** wird je Anzeige neu erzeugt und **nie** gespeichert — nicht in `Schrittergebnis`, `Kommentar`, `Protokoll` oder der Druckansicht. - **Erweiterbarkeit:** Kein Codepfad nimmt an, dass ein Schritt nur von einem Menschen erledigt wird. `SchrittTyp` bekommt später `AKTION`, `konfig` und `wert` sind Json. ## 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). ## 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`. 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. ## Vor jedem Commit ```bash pnpm lint && pnpm typecheck && pnpm test && pnpm pruefe:doku ``` 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`). Druckansichten und Etiketten immer hell. **Am Handy nie horizontales Scrollen** — bei 390 px geprüft.