# Team-Chat: v1-Produkt- und UX-Vertrag **Stand:** 2026-07-15 **Ticket:** `AK-P15b` / HIVEGRID-45 **Architektur:** Bespoke WebSocket-Chat nach AD-9, kein Hocuspocus Dieses Dokument konkretisiert die bereits beauftragten Chat-Funktionen als belastbaren Produktvertrag. Es trennt notwendige Ausführungsqualität von späteren Erweiterungen und verhindert, dass die Interim-UI einen technisch vollständigen, aber im Alltag hakeligen Chat verdeckt. ## 1. Produktprinzipien 1. **Sofort reagieren, serverseitig entscheiden.** Senden, Reactions und Pins erscheinen optimistisch. Die Datenbank bleibt autoritativ; Fehler bleiben am betroffenen Element sichtbar und können dort erneut versucht werden. 2. **Kein Verlust und keine Duplikate.** Jeder Schreibvorgang besitzt vor dem ersten Versuch eine stabile Client-ID. Wiederholungen mit derselben ID liefern dasselbe Resultat. 3. **Realtime ist ein Beschleuniger, nicht die Datenbank.** Redis verteilt Events; nach Reconnect werden Lücken per Cursor aus PostgreSQL geschlossen. 4. **Ruhe beim Lesen.** Neue Nachrichten reißen Nutzer nicht ans Listenende. Wer hochgescrollt hat, sieht einen Hinweis mit Anzahl und Sprungaktion. 5. **Mobile und Tastatur sind Primärpfade.** Alle Aktionen funktionieren per Touch, Tastatur und Screenreader. ## 2. Nachrichten- und Reconnect-Vertrag ### Optimistisches Senden - UI-Zustände: `sending → sent` oder `sending → failed`. Einen erfundenen gerätebezogenen `delivered`-Zustand gibt es in v1 nicht. - `clientMessageId` wird vor dem ersten Versuch erzeugt und bei Retry wiederverwendet. Der Server hält `(tenantId, authorUserId, clientMessageId)` eindeutig und antwortet bei Wiederholung mit derselben Nachricht. - Die Local-Echo-Nachricht erscheint sofort mit dezentem Sendestatus. Server-Antwort und WebSocket-Echo werden gegen die Client-ID abgeglichen und dürfen weder doppelte Zeilen noch doppelte Reaktionen erzeugen. - Fehler entfernen den Text nicht. Direkt an der Nachricht stehen „Erneut versuchen" und „Verwerfen"; kein Toast-Spam. Automatischer Retry gilt nur für Timeout, `429` mit `Retry-After` und `5xx`, mit Backoff und Jitter. - Innerhalb eines Channels/Threads bleibt die Reihenfolge stabil; unabhängige Channels dürfen parallel senden. ### Verbindung und Gap-Fill - Sichtbare Zustände: `connecting`, `online`, `reconnecting`, `offline`, `blocked`. - Nach Reconnect sendet der Client den letzten bestätigten Cursor und erhält verpasste persistente Events in stabiler Reihenfolge. Erst danach gilt die Verbindung als vollständig synchronisiert. - Presence, Typing und Transportstatus sind flüchtig und werden nicht als Chat-Historie gespeichert. - Queue-Grenzen und `WebSocket.bufferedAmount` werden überwacht, weil der klassische Browser-WebSocket keine Backpressure bereitstellt. - Vollständiges Offline-Senden über Reload oder Background Sync ist nicht Teil von v1. Entwürfe bleiben lokal erhalten; ein offline ausgelöster Send bleibt sichtbar fehlgeschlagen und kann nach Reconnect wiederholt werden. ## 3. Lesen, Scrollen und Entwürfe - History wird cursorbasiert paginiert. Beim Nachladen älterer Nachrichten bleibt der visuelle Scroll-Anker stabil. - Automatisches Scrollen ans Ende erfolgt nur, wenn der Nutzer bereits am Ende ist oder selbst sendet. Sonst erscheint „N neue Nachrichten". - Read-Cursor sind monoton. Eine Nachricht wird nur als gelesen gewertet, wenn Channel und Dokument aktiv sind und die Nachricht sichtbar war; Updates werden gebündelt. Eigene Nachrichten erzeugen kein Unread. - Channel zeigen getrennt Gesamt-Unread und Mention-Marker. Mute unterdrückt Benachrichtigungen, aber niemals Unread; direkte User-Mentions und DMs umgehen Mute gemäß aktivem Chat-Vertrag (`AK-P15b`). - Der Composer speichert Textentwürfe debounced pro `Tenant + User + Channel` lokal und tenant-isoliert. Ein Entwurf wird erst nach erfolgreichem Server-Ack gelöscht. Datei-Binaries landen nicht im Local Storage. ## 4. Composer und Nachrichten-QoL - `Enter` sendet, `Shift+Enter` erzeugt eine neue Zeile. Während IME-Composition darf `Enter` nicht senden. - Markdown bleibt auf den freigegebenen Umfang beschränkt und wird serverseitig sanitisiert. - Eigene Nachrichten können innerhalb von 24 Stunden editiert werden; Änderungen tragen einen „bearbeitet"-Marker. Löschen erzeugt einen Tombstone, damit Threads und Antworten verständlich bleiben. - Hover-Aktionen sind identisch per Fokus und Mobile-Long-Press erreichbar: Reaktion, Thread, Bearbeiten, Löschen, Pin und Als-Aufgabe-markieren gemäß Berechtigung. - Mentions verwenden eine zugängliche Combobox mit Pfeiltasten, `Enter` und `Escape`. Autocomplete und Benachrichtigung zeigen beziehungsweise adressieren nur Nutzer, die den Channel lesen dürfen. - Reactions werden optimistisch gesetzt und bei Fehler zurückgerollt. Gleiche Reaktion eines Users auf dieselbe Nachricht ist eindeutig. - Voice Notes sind einfache Audio-Anhänge mit Aufnahme-/Upload-Fortschritt, Dauer, Abspielen, Abbrechen und Retry. Es gibt kein Transkript und keine KI-Verarbeitung. - Uploads nutzen die bestehende Files-Pipeline inklusive AV-Prüfung. Bilder unterstützen Vorschau und verständlichen Alternativtext; Mobile bietet den direkten Kamera-Input. ## 5. Presence, Typing, Threads und Pins - Typing startet erst nach echter Eingabe, wird gedrosselt, läuft nach kurzer TTL aus und endet bei Senden, Leeren, Channelwechsel oder Disconnect. Die UI zeigt höchstens zwei Namen plus „N weitere". - Presence wird aus aktiven Sessions abgeleitet und läuft über TTL aus; sie ist kein belastbarer Arbeitszeitnachweis. - Threads haben genau eine Ebene. Desktop nutzt ein Seitenpanel, Mobile eine eigene Detailansicht. Wer erstellt, antwortet oder erwähnt wird, folgt dem Thread automatisch. - Message-Pins sind in Group- und Projekt-Channels auf 50 begrenzt. Nach bestätigtem Produktentscheid gibt es in DMs keine Message-Pins. Der persönliche Channel-Pin bleibt ein getrenntes Modell. ## 6. Benachrichtigungen und bestätigte v1-Grenzen - Mentions, DMs und Thread-Antworten laufen über `notify()` und öffnen per Deep Link die exakte Nachricht. - Der allgemeine Browser-Web-Push inklusive Permission-Prompt bleibt bei `VA-P27a`; Chat-v1 liefert In-App- Notifications und Deep Links. - Tenant-Admin-Audit liegt unter `/settings/chat/audit`. Eine kurzlebige Audit-Session verlangt einen Grund; jeder fremde Lesezugriff wird auditiert und DMs zeigen den Audit-Banner. - `FileEntityType` wird minimal um `chat_message` erweitert, damit Anhänge die bestehende `FileEntityLink`- Pipeline nutzen und kein paralleles Datei-System entsteht. ## 7. Accessibility und Mobile - Die Nachrichtenliste ist ein benanntes `role="log"` ohne pauschales Live-Announcement. Separate, dosierte `aria-live="polite"`-Statusbereiche melden nur neue fremde Nachrichten; initial geladene Historie und eigene Local Echoes werden nicht doppelt angesagt. - Verbindungs- und Sendestatus verwenden einen separaten Statusbereich und nie nur Farbe. - Fokus bleibt nach Send im Composer; Drawer/Dialog geben Fokus kontrolliert zurück. Channel-, Message- und Picker-Listen sind vollständig per Tastatur bedienbar. - Mobile verwendet Master/Detail-Navigation, mindestens 44 Pixel große Touch-Ziele, Safe-Area am sticky Composer und behält Draft sowie Scrollposition beim Channelwechsel. ## 8. Wiederverwendung des HiveGrid-UI-Baukastens | Fläche | Vorhandene Bausteine | Neuer chat-spezifischer Verbund | |---|---|---| | Shell | `SubframeAppShell`, `PageShell`, `SidebarInside`, `ResizablePanel` | `ChatWorkspace` | | Channel-Liste | `SearchInput`, `Avatar`, `BadgeFeedback`, `DropdownMenu`, `ContextMenu`, `Tooltip` | `ChatChannelRow` | | Verlauf | `Avatar`, `Button`, `IconButton`, `ContextMenu`, `ScrollArea`, `Skeleton` | `ChatMessageList`, `ChatMessageRow` | | Composer | `TextArea`, `IconButton`, `Progress`, `BannerOffline` | `ChatComposer`, `ChatAttachmentTray` | | Threads | `Drawer`, `Dialog`, `Accordion` | `ThreadDrawer` | | Mentions/Suche | `SearchInput`, `Command`, `Avatar` | `MentionAutocomplete`, `ChatSearchResults` | | States | `EmptyState`, `Alert`, `BannerOffline`, `Skeleton`, `LoadingIndicatorSkeleton` | nur Chat-Kompositionen | | Medien | `uploadFileInChunks`, `AudioOrVideoPlayer`, `Progress` | Voice-Note- und Attachment-Steuerung | Die bestehenden visuellen Primitives werden direkt genutzt. Nicht wiederverwendet werden die rein visuellen, nicht interaktiven `FileUpload`-/`Combobox`-Attrappen und der für Chat zu schwere Legacy-Rich-Text-Editor. ## 9. Bewusst später - persistente Offline-Outbox und Background Sync über Reload - Scheduled Messages, Erinnerungen und „Für später speichern" - gerätebezogener `delivered`-Status und Read-Receipt-Privacy-Schalter - Slack-Power-Shortcuts, frei konfigurierbares Sendeverhalten und Pop-out-Threads - server-/geräteübergreifend synchronisierte Drafts - GIF-/Sticker-Apps, Huddles, Audio-/Videoanrufe und KI-Transkription ## Research-Quellen - [React `useOptimistic`](https://react.dev/reference/react/useOptimistic) - [Matrix Client-Server API: Transaction IDs, Local Echo, Receipts, Typing, Threads](https://spec.matrix.org/latest/client-server-api/) - [MDN WebSocket API und fehlende Backpressure](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) - [Slack: Send, read and manage drafts](https://slack.com/help/articles/201457107-Send-and-read-messages) - [Slack: Threads](https://slack.com/help/articles/115000769927-Use-threads-to-organize-discussions) - [Slack: Keyboard navigation](https://slack.com/help/articles/115003340723-Navigate-Slack-with-your-keyboard) - [W3C ARIA23: `role=log`](https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA23) - [WAI-ARIA Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/)