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

5.3 KiB

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

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.
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:

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:

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.