Steven
Steven5 Min. Lesezeit

Wie man einen Live-Bericht ohne das Flackern streamt

Wenn dein Meeting endet, füllt sich die Zusammenfassung von GeekBye jetzt live auf, statt dich auf einen Spinner starren zu lassen. Eine Streaming-Oberfläche ruhig statt zappelig wirken zu lassen, erforderte, das Flackern zweimal zu lösen — einmal für strukturierte Felder, einmal für Markdown — und die zweite Lösung war ein Renderer, den wir bereits besaßen.

Technik
Frontend
Streaming
GeekBye-Releases
Wie man einen Live-Bericht ohne das Flackern streamt

Vor diesem Release bedeutete das Beenden eines GeekBye-Meetings, auf einen Spinner zu starren. Die App schickte dein gesamtes Transkript zur Analyse, wartete auf die komplette Zusammenfassung — Bewertung, Kernpunkte, Aufgaben, alles — und ließ sie dann auf einen Schlag auf den Bildschirm fallen. Funktional, aber es fühlte sich langsam an, weil du ins Leere starrtest, während sie arbeitete.

GeekBye v1.6.13–v1.6.15 verwandelte das in einen Bericht, der live hereinstreamt, Feld für Feld, während er generiert wird. Der interessante Teil ist nicht das Streaming — es ist alles, was wir tun mussten, um das Streaming davon abzuhalten, zappelig auszusehen. Ruhiges Streaming und ruckeliges Streaming sind dieselbe Funktion mit sehr unterschiedlicher Umsetzung.

Flackern, Teil eins: lass nicht jeden Chunk React durchschütteln

Die Zusammenfassung ist kein einzelner Klumpen — es sind strukturierte Felder (eine Gesamtbewertung, Kernpunkte, Aufgaben und so weiter), die das Backend eines nach dem anderen über server-sent events ausgibt. Der naive Weg, das zu rendern, ist, den React-State bei jedem Ereignis zu aktualisieren, sobald es eintrifft.

Tu das, und du bekommst Flackern. Jedes eintreffende Feld löst sein eigenes Neu-Rendern aus; Komponenten, die „leer" zeigten, werden montiert, dann ausgehängt, dann als „geladen" neu montiert; das Layout zuckt bei jedem Paket. Der Bericht setzt sich vor deinen Augen selbst zusammen — auf die denkbar schlechteste Art, sichtbar, nervös.

Die Lösung ist eine zweiteilige Disziplin. Erstens: sammle die eintreffenden Felder in einem ref — einem veränderlichen Halter, der keine Renders auslöst — und veröffentliche einmal pro Chunk mit einer frischen Kopie in den React-State, damit sich der Baum bewusst aktualisiert statt bei jedem Mikro-Ereignis. Zweitens: rendere die ganze Zeit einen einzigen stabilen Komponentenbaum, ob im Streaming oder fertig, mit Inline-Platzhaltern für Felder, die noch nicht eingetroffen sind:

// accumulate in a ref, publish once per chunk
partialRef.current = { ...partialRef.current, [field]: value }
setPartial({ ...partialRef.current })

// one tree, never swapped; missing fields show a placeholder in place
const report = isStreaming ? partial : saved
<Score>{report.overallScore ?? '…'}</Score>

Die Komponente, die die Bewertung anzeigt, wird nie ausgehängt — sie zeigt einfach , bis die Zahl landet, dann die Zahl. Nichts zuckt. Und das vollständig zusammengesetzte Objekt wird am Ende trotzdem in der lokalen Datenbank zwischengespeichert, sodass Streaming-Oberfläche und dauerhafte Speicherung koexistieren, ohne sich zu bekämpfen.

Flackern, Teil zwei: der Renderer, den wir bereits besaßen

Das zweite Flackern ist subtiler und lebt im Markdown selbst. Der Bericht rendert formatiertes Markdown — Überschriften, Fettdruck, Listen, Code-Blöcke. Aber Markdown, das mitten im Stream eintrifft, ist in jedem gegebenen Moment halbfertig. Eine Liste mit bisher einem Aufzählungspunkt. Ein Code-Zaun, der geöffnet, aber nicht geschlossen wurde. Ein Fett-Marker noch ohne Partner.

Ein herkömmlicher Markdown-Renderer parst bei jedem Token die gesamte Zeichenkette neu, und dieser halbfertige Zustand parst jedes Mal zu etwas anderem — also flackert die Liste, der Code-Block blitzt auf und zu, das Layout springt, während die Tokens fertig werden. Es ist dieselbe nervöse Montage wie zuvor, eine Ebene tiefer.

Die Lösung war fast peinlich verfügbar: wir nutzten bereits einen streaming-bewussten Markdown-Renderer für den KI-Live-Chat. Ein Renderer, gebaut, um unvollständige Tokens zu vertragen — halbfertiges Markdown stabil zu rendern und es erst festzulegen, wenn die Tokens fertig sind — statt jedes Mal von Grund auf neu zu parsen. Der Bericht musste nur denselben verwenden. Wir hatten genau dieses Problem Monate zuvor für den Chat gelöst; die „Lösung" für den Bericht war zu erkennen, dass wir das Werkzeug bereits hatten, und es auf eine zweite Oberfläche zu richten. Wiederverwenden schlug Neubauen.

Der Bonus: formatiertes Kopieren ist ein Zwischenablage-Format-Problem

Sobald der Bericht gut aussah, wollten die Leute ihn einfügen — in ein Dokument, eine E-Mail — und die Formatierung und die Screenshots behalten. Der Instinkt ist, den Bericht in eine Markdown-Zeichenkette zu serialisieren und zu hoffen, dass das Ziel sie rendert. Meistens tut es das nicht.

Die wahre Antwort ist, dass die Zwischenablage mehrere Darstellungen gleichzeitig hält. Also schreiben wir zwei: eine HTML-Version mit intakter Formatierung und den als Inline-Bilder eingebetteten Screenshots und einen Nur-Text-Fallback für Ziele, die kein HTML annehmen. Füge in einen Rich-Editor ein und du bekommst den formatierten Bericht mit Bildern; füge in ein Nur-Text-Feld ein und du bekommst sauberen Text. „Formatiertes Kopieren" war nie ein Serialisierungsproblem — es war ein Problem des Das-richtige-Zwischenablage-Format-Wählens.

Dasselbe Release gab dem Bericht außerdem Chat-artige Ich / Sie-Beschriftungen mit den beiden Sprechern an gegenüberliegenden Seiten ausgerichtet, sodass sich ein Transkript wie das Gespräch liest, das es war, und verschob deine eigenen Screenshots passend auf ihre eigene Seite. (Ein Release in diesem Fenster, v1.6.14, war ein reiner Neubau — keine Geschichte darin, und es ist ehrlich, das zu sagen.)

Drei Dinge, die uns die Streaming-Oberfläche lehrte

  1. Puffere gestreamte Felder, veröffentliche einmal pro Chunk. Jedes server-sent event seine eigene State-Aktualisierung antreiben zu lassen, ist das Flackern. Ein ref-Akkumulator plus eine einzige Veröffentlichung pro Chunk, in einen Baum, der nie ausgehängt wird, verwandelt nervöse Montage in ruhiges Auffüllen.
  2. Alles, was streamt, braucht einen streaming-bewussten Renderer. Ein Renderer, der pro Token die gesamte Zeichenkette neu parst, flackert bei halbfertigem Markdown. Nutze einen, der für unvollständige Eingaben gebaut ist — und wenn du schon einen für eine andere Oberfläche hast, verwende ihn wieder, bevor du einen zweiten baust.
  3. Formatiertes Kopieren dreht sich um Zwischenablage-Formate, nicht um Serialisierung. Schreibe text/html mit eingebetteten Bildern und einen text/plain-Fallback. Die Zwischenablage wurde entworfen, um beides zu tragen; nutze sie.

Dies ist das fünfte Kapitel der Zuverlässigkeits- und Feinschliff-Geschichte, die zu GeekBye v2 wird. Für das vorherige Kapitel siehe den Ohrhörer-Bug (v1.6.12); für die Stelle, an der dieselbe server-sent-events-Idee später zu einem ganzen Fallback-Transport wurde, Live-Transkription, wenn die Firewall WebSockets blockiert (v2.0.8); und für den gesamten Bogen die Anatomie, Software bis zur Perfektion auszuliefern.

Ähnliche Artikel

Ruhige Software: einen flackernden Schalter beseitigen und dem Chat beibringen, in welchem Modus er ist
Steven
Steven6 Min. Lesezeit

Ruhige Software: einen flackernden Schalter beseitigen und dem Chat beibringen, in welchem Modus er ist

Zwei kleine GeekBye-Releases, keine Vorzeigefunktionen — nur ein Einstellungs-Schalter, der aufgehört hat zu flackern, und ein Chat-Assistent, der dir endlich sagt, ob er im Meeting-Modus oder im Coding-Modus geantwortet hat. Genau das kostet "ruhige Software" wirklich, ein Detail nach dem anderen.

Produkt
Design
Technik
Eine abgebrochene Verbindung sollte nicht die ganze App zum Absturz bringen — unsere tat es
Steven
Steven6 Min. Lesezeit

Eine abgebrochene Verbindung sollte nicht die ganze App zum Absturz bringen — unsere tat es

Als unser Backend mitten in einem Meeting offline ging, hat es nicht nur die Transkription pausiert — es hat die ganze App zum Absturz gebracht. Die Ursache war ein einziges nicht behandeltes Event, und der Fix waren zehn Zeilen. Das ist das Release-Cluster, das GeekBye angemeldet bleiben und verbunden bleiben ließ, durch genau die Dinge, die es früher umgebracht haben.

Engineering
Zuverlässigkeit
Electron
Eine CSS-Variable, fünf Review-Runden und eine Swift-Toolchain, die log
Steven
Steven7 Min. Lesezeit

Eine CSS-Variable, fünf Review-Runden und eine Swift-Toolchain, die log

GeekBye v2.0.7 machte das gesamte Overlay verstellbar durchscheinend — was nach einer einzeiligen CSS-Änderung klingt und definitiv keine war. Die eigentliche Geschichte ist, was das Code-Review aufdeckte: zwei Flächen, die sich weigerten zu verblassen, eine Aufnahmeleiste, die bei gleicher Deckkraft falsch aussah, und ein kompiliertes Binary, das um 672 Bytes hin und her sprang, weil unsere eigene Dokumentation einem Reviewer die falsche Swift-Version nannte.

Engineering
Design
Build