Fachdokumente nach docs/, Entscheidungen festgehalten

CLAUDE.md und README.md verweisen auf docs/, die Dateien lagen aber im
Wurzelverzeichnis. Verschoben.

docs/13-entscheidungen.md hält fest, wo die Dokumente sich widersprechen und
wie es aufgelöst wurde. Der wichtigste Punkt: 04-datenmodell.md verlangt eine
Regel, die jedes UPDATE auf CaseEntryVersion verwirft, 05-akten-und-vorlagen.md
verlangt, dass eine Umklassifizierung rückwirkend auf alle Versionen wirkt.
Beides zusammen geht nicht. Aufgelöst über einen Trigger, der die Tabelle
anhängend hält, aber das Anheben von minClearance zulässt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Harlekin
2026-09-01 00:00:58 +02:00
co-authored by Claude Opus 5
commit 4446e3f0ac
16 changed files with 1919 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
# Chat und Echtzeit
## Zwei Ebenen
- **Aktenchat**: hängt an einer Akte, formlos, ausdrücklich kein Aktenbestandteil.
Optisch klar vom Verlauf abgesetzt (siehe Design-System).
- **Kanäle**: frei angelegte Räume für allgemeine Absprachen, mit eigener Mindeststufe.
## Kernfunktion: „Als Vermerk übernehmen"
Eine Chatnachricht wird per Klick zu einem `CaseEntry` mit `type = NOTE`. Der erzeugte
Eintrag trägt einen Hinweis auf den Ursprung (Autor, Zeitpunkt, Nachrichten-ID). Die
Chatnachricht bleibt stehen und bekommt eine Markierung „übernommen".
Das ist die Brücke zwischen Discord-Gewohnheit und sauberem Aktenbestand und sollte
prominent sein, nicht in einem Kontextmenü versteckt.
## Funktionsumfang
- Erwähnungen `@` mit Autovervollständigung aus `User`
- Antworten auf einzelne Nachrichten (`replyToId`), zitierte Vorschau
- Reaktionen (kleiner fester Satz, keine freie Emoji-Auswahl)
- Bearbeiten mit sichtbarem `editedAt`
- Löschen nur als Zurückziehen: `withdrawnAt` gesetzt, Platzhalter „Nachricht zurückgezogen"
- Ungelesen-Markierung über `ReadMarker`, Trennlinie „Neu seit deinem letzten Besuch"
## Aktenzeichen-Erkennung
Text wie `AfiS-2026-0038` wird beim Rendern zu einer Vorschaukarte. **Die Karte wird
serverseitig aufgelöst und respektiert die Stufe des Lesers.** Wer die Akte nicht sehen darf,
bekommt eine neutrale Karte ohne Titel und ohne Status. Nie den Titel mitschicken und im
Client ausblenden.
## Echtzeit über SSE
Kein WebSocket, kein Redis. Postgres `LISTEN/NOTIFY` plus ein SSE-Endpunkt reicht bei dieser
Nutzerzahl vollständig.
```
GET /api/stream?topics=case:<id>,notifications
```
- Ein einziger `pg`-Client hält `LISTEN` offen und verteilt an die Verbindungen im Prozess
- Server sendet alle 25 Sekunden einen Kommentar-Heartbeat gegen Proxy-Timeouts
- Client nutzt `EventSource` mit `Last-Event-ID`, bei Reconnect werden verpasste Nachrichten
über `?since=<id>` nachgeladen
- Reconnect mit exponentiellem Backoff, gedeckelt bei 30 Sekunden
- Beim Wechsel des Tabs in den Hintergrund wird die Verbindung nach 5 Minuten geschlossen und
bei Rückkehr neu aufgebaut
Ereignistypen: `chat.new`, `chat.edit`, `entry.new`, `entry.updated`, `entry.retracted`,
`case.update`, `attachment.scanned`, `presence`.
`entry.updated` und `entry.retracted` tauschen den Eintrag im offenen Verlauf anhand der ID
aus. Wer die Akte gerade offen hat, sieht die Änderung sofort und nicht erst beim Neuladen.
## Dedupe mit Client-IDs
Der Client erzeugt die Nachrichten-ID (UUIDv7) selbst und zeigt die Nachricht sofort an.
Kommt dieselbe ID über SSE zurück, wird der optimistische Eintrag ersetzt statt verdoppelt.
Die ID ist gleichzeitig der Idempotenzschlüssel: ein wiederholtes Absenden nach Netzfehler
erzeugt keine zweite Nachricht (`INSERT ... ON CONFLICT DO NOTHING`).
## Discord-Benachrichtigungen
Der Worker postet in einen konfigurierbaren Channel und optional als DM an Beteiligte:
> Neuer Eintrag in **AfiS-2026-0042** · Insp. Vogel · https://afis.example.de/faelle/AfiS-2026-0042
**Niemals Inhalte, niemals Titel bei Akten ab Stufe 3.** Discord kennt eure Klassifizierung
nicht. Der Link führt zur App, die dann selbst prüft.
Konfigurierbar pro Nutzer: alles / nur meine Fälle / nur Erwähnungen / aus.