Files
betreuungs-cockpit/docs/KONVENTIONEN.md
T
Florian Wilde dcd3a8ce71 feat: Oberflaechenbausteine, Anwendungsrahmen und Konventionen
Basisbausteine im shadcn/ui-Stil, aber mit drei bewussten Abweichungen:

- Bedienelemente sind 44px hoch statt 36px. Ich bediene die App oft am
  Handy im Heim oder auf dem Amt; kleiner ist dort nicht treffsicher.
- Auswahlfelder benutzen das native <select>. Android und iOS oeffnen
  dafuer ihre eigene Liste - schneller als jeder Nachbau, und mit
  Screenreader und Tastatur funktioniert es ohne Zutun.
- Eingabefelder haben mindestens 16px Schrift, sonst zoomt iOS-Safari
  beim Hineintippen das Formular heran.

<Feld> verdrahtet Beschriftung, Erklaerung und Fehler ueber
aria-describedby. Der Erklaerungstext ist kein Beiwerk: die Anwendung
benutzt Begriffe aus dem Betreuungsrecht, die niemand nachschlagen soll.

<Rechtshinweis> ist der stehende Vermerk, dass hier keine Rechtsberatung
stattfindet. Er steht an jeder Stelle mit Paragraphenbezug.

Der Anwendungsrahmen fuehrt drei Bedingungen zusammen, ohne die niemand
an Fachdaten kommt: angemeldet, zweiter Faktor eingerichtet, mindestens
eine betreute Person zugeordnet. Die Navigation zeigt Zaehler fuer
ueberfaellige Fristen und neuen Posteingang - die Frage "wo muss ich
hin?" wird schon im Menue beantwortet.

Das Farbschema wird per Inline-Skript mit CSP-Nonce gesetzt, bevor die
Seite gezeichnet wird - sonst blitzt beim Laden das helle Thema auf.

docs/KONVENTIONEN.md haelt fest, was in allen weiteren Etappen gilt:
Verzeichnisaufbau, Cent-Betraege, Umgang mit verschluesselten Feldern,
Zugriffspruefung, Audit-Pflicht, Ampelfarben, Commit-Trailer.

Etappe: 1
Phase: Fundament
2026-08-22 01:54:56 +02:00

161 lines
5.3 KiB
Markdown

# Konventionen im Betreuungs-Cockpit
Verbindlich für jeden, der Code in dieses Projekt schreibt. Wer davon
abweicht, erzeugt Reibung an der nächsten Schnittstelle.
## Sprache
- **Alles auf Deutsch**: Bezeichner, Kommentare, Commit-Nachrichten,
Oberflächentexte, Fehlermeldungen. Ausnahmen sind Framework-Vorgaben
(`page.tsx`, `layout.tsx`, `default`, `props`) und etablierte
Fachbegriffe ohne gute Übersetzung (`Token`, `Hash`, `Cookie`).
- Umlaute in **Oberflächentexten**: ja, korrekt geschrieben.
Umlaute in **Bezeichnern und Dateinamen**: nein, umschrieben
(`vermoegen`, `naechsterSchritt`, `zurueck`).
- Du-Ansprache. Keine Juristensprache ohne Erklärung — steht ein
Paragraph im Text, gehört eine Erklärung daneben.
## Verzeichnisse
```
src/
app/
(auth)/ Anmeldung, Ersteinrichtung, Zweitfaktor
(app)/ Alle Fachseiten, hinter Anmeldung + 2FA
api/
components/
ui/ Basisbausteine (Button, Input, Card, …)
<bereich>/ Fachkomponenten je Modul
lib/
crypto/ Feldverschlüsselung, Hashing, TOTP
auth/ Sitzung, Rollen, Rate-Limiting
validierung/ Zod-Schemata je Entität
<bereich>/ Fachlogik ohne React
server/
actions/ Server Actions ("use server")
prisma/
tests/ Vitest (Logik)
e2e/ Playwright (kritische Flows)
```
Ein Modul bringt mit: Seite(n) unter `app/(app)/<modul>/`, Fachkomponenten
unter `components/<modul>/`, Zod-Schema unter `lib/validierung/<modul>.ts`,
Server Actions unter `server/actions/<modul>.ts`, reine Logik unter
`lib/<modul>/`.
## Datenzugriff
```ts
import { db, nichtGeloescht } from '@/lib/db'
```
- **Jede** Abfrage auf Fachdaten filtert `...nichtGeloescht` (Papierkorb).
- **Jede** Abfrage ist auf `betreuteId` eingegrenzt.
- Zugriff wird immer über `erfordereZugang(betreuteId, modul, art)` geprüft
— auch in Server Actions, nicht nur in der Seite. Nie nur im UI ausblenden.
```ts
const { betreute, benutzer, darfSchreiben } = await erfordereZugang(id, 'FRISTEN', 'schreiben')
```
## Geldbeträge
Immer `BigInt` in **Cent**, Feldname endet auf `Cent`. Anzeige über
`formatCent()`. Nie `number` für Beträge, nie `Decimal`, nie Fließkomma.
Auf dem Weg zu Client Components über `centZuZahl()`.
## Datum
- Fristen, Stichtage, Bescheiddaten: `@db.Date`, ohne Uhrzeit.
- Zeitstempel (`erstelltAm`, Protokolle): volles `DateTime`.
- Anzeige über `formatDatum()` / `formatDatumZeit()` aus `@/lib/utils`.
- Datumsrechnung immer über `date-fns`, nie mit `setDate` von Hand.
## Verschlüsselte Felder
Feldname endet auf `Verschluesselt`. Nie direkt schreiben:
```ts
import { sealen, oeffnen } from '@/lib/crypto/feld'
const daten = { ...rest, ...sealen('Vorgang', { beschreibung: eingabe.beschreibung }) }
const offen = oeffnen('Vorgang', datensatzAusDb) // offen.beschreibung ist Klartext
```
Neues verschlüsseltes Feld? Erst in `VERSCHLUESSELTE_FELDER` in
`src/lib/crypto/feld.ts` eintragen, sonst wirft `sealen()`.
Verschlüsselte Felder lassen sich **nicht** filtern oder sortieren.
## Audit-Log
Jede schreibende Aktion protokolliert:
```ts
await protokollieren({
aktion: 'GEAENDERT',
entitaet: 'Frist',
entitaetId: frist.id,
bezeichnung: frist.titel,
betreuteId,
userId: benutzer.id,
userEmail: benutzer.email,
userName: benutzer.name,
aenderung: aenderungErmitteln(vorher, nachher),
})
```
## Server Actions
- Immer `'use server'` oben, immer Zod-Validierung, immer Zugriffsprüfung.
- Rückgabe für Formulare: ein Zustandsobjekt
`{ fehler?: string; felder?: Record<string, string>; werte?: … }`,
benutzt mit `useActionState`. Keine geworfenen Fehler ans Formular.
- Nach dem Schreiben `revalidatePath()` auf die betroffene Seite.
- Löschen heißt: `geloeschtAm` setzen. Endgültiges Löschen nur im
Papierkorb und nur nach `erfordereFrischeAnmeldung()`.
## Oberfläche
- Basisbausteine aus `@/components/ui/*`. Formularfelder immer in
`<Feld id beschriftung erklaerung fehler>` einwickeln — das verdrahtet
Label, Erklärung und Fehler barrierefrei.
- Jede Seite beginnt mit `<SeitenKopf titel beschreibung pfad aktionen />`.
- Leere Listen bekommen `<Leerzustand>` mit einem konkreten nächsten
Schritt, nie nur "keine Daten".
- Steht ein Paragraph auf der Seite, steht `<Rechtshinweis>` darunter.
- Mindestens 44 px Touchziele, Schrift nie unter 16 px in Eingabefeldern.
- Farbe ist nie die einzige Information — Ampeln immer mit Text.
- Tabellen scrollen im eigenen Rahmen, nie die Seite.
- Keine externen Fonts, keine CDNs, keine Tracker. Bilder als Data-URI
oder aus dem eigenen Objektspeicher.
## Statusfarben (Ampel)
| Lage | Variante |
|---|---|
| überfällig | `rot` |
| diese Woche fällig | `orange` |
| dieser Monat fällig | `gelb` |
| erledigt / im Plan | `gruen` |
## Tests
- Fachlogik (Fristberechnung, Beträge, Bewertungen): Vitest unter `tests/`.
- Kritische Abläufe: Playwright unter `e2e/`.
- Ein Test beschreibt auf Deutsch, was fachlich gelten soll — nicht, welche
Funktion aufgerufen wird.
## Commits
- Deutsch, Imperativ, erste Zeile ≤ 72 Zeichen.
- Körper erklärt **warum**, nicht was.
- Trailer am Ende, ohne Leerzeile dazwischen:
```
Etappe: 3
Phase: Papier
```
Diese Trailer wertet `npm run protokoll` aus — daraus entsteht das
Bauprotokoll mit den Zeiten je Etappe.