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
161 lines
5.3 KiB
Markdown
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.
|