Steven
Steven6 min de lecture

Comment diffuser un rapport en direct sans le scintillement

Quand votre réunion se termine, le résumé de GeekBye se remplit désormais en direct au lieu de vous faire fixer un spinner. Rendre une interface en streaming apaisée plutôt que nerveuse a demandé de résoudre le scintillement deux fois — une fois pour les champs structurés, une fois pour le markdown — et le second correctif était un moteur de rendu que nous possédions déjà.

Ingénierie
Frontend
Streaming
Sorties GeekBye
Comment diffuser un rapport en direct sans le scintillement

Avant cette version, terminer une réunion GeekBye revenait à fixer un spinner. L''app envoyait toute votre transcription pour analyse, attendait le résumé entier — score, points clés, actions à mener, tout — puis le déposait à l''écran d''un seul coup. Fonctionnel, mais ça paraissait lent, parce que vous fixiez le vide pendant qu''elle travaillait.

GeekBye v1.6.13–v1.6.15 a transformé cela en un rapport qui se diffuse en direct, champ par champ, à mesure qu''il se génère. La partie intéressante n''est pas le streaming — c''est tout ce que nous avons dû faire pour l''empêcher de paraître nerveux. Le streaming apaisé et le streaming saccadé sont la même fonctionnalité avec des exécutions très différentes.

Scintillement, partie un : ne laissez pas chaque fragment secouer React

Le résumé n''est pas un seul bloc — ce sont des champs structurés (un score global, des points clés, des actions à mener, et ainsi de suite) que le backend émet un à un via des server-sent events. La façon naïve de rendre cela est de mettre à jour l''état de React à chaque événement dès qu''il arrive.

Faites cela, et vous obtenez du scintillement. Chaque champ qui arrive déclenche son propre re-rendu ; des composants qui affichaient « vide » se montent, puis se démontent, puis se remontent en « chargé » ; la mise en page tressaute à chaque paquet. Le rapport s''assemble lui-même sous vos yeux de la pire façon possible — visiblement, nerveusement.

Le correctif est une discipline en deux parties. D''abord, accumulez les champs entrants dans un ref — un conteneur mutable qui ne déclenche pas de rendus — et publiez vers l''état de React une seule fois par fragment avec une copie fraîche, pour que l''arbre se mette à jour délibérément plutôt qu''à chaque micro-événement. Ensuite, rendez un unique arbre de composants stable tout du long, qu''il soit en streaming ou terminé, avec des marqueurs en ligne pour les champs qui ne sont pas encore arrivés :

// 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>

Le composant qui affiche le score ne se démonte jamais — il affiche simplement jusqu''à ce que le nombre atterrisse, puis le nombre. Rien ne tressaute. Et l''objet entièrement assemblé est quand même mis en cache dans la base de données locale à la fin, si bien que l''interface en streaming et le stockage durable cohabitent sans se battre.

Scintillement, partie deux : le moteur de rendu que nous possédions déjà

Le second scintillement est plus subtil et vit dans le markdown lui-même. Le rapport rend du markdown enrichi — titres, gras, listes, blocs de code. Mais le markdown qui arrive en plein flux est, à tout instant donné, à moitié terminé. Une liste avec une seule puce pour l''instant. Un bloc de code ouvert mais pas fermé. Un marqueur de gras encore sans partenaire.

Un moteur de rendu markdown classique réanalyse toute la chaîne à chaque token, et cet état à moitié terminé s''analyse en quelque chose de différent à chaque fois — donc la liste scintille, le bloc de code clignote en s''ouvrant et se fermant, la mise en page saute à mesure que les tokens se complètent. C''est le même assemblage nerveux qu''avant, une couche plus bas.

Le correctif était presque gênant de disponibilité : nous utilisions déjà un moteur de rendu markdown conscient du streaming pour le chat IA en direct. Un moteur conçu pour tolérer des tokens incomplets — pour rendre du markdown à moitié terminé de façon stable et ne le figer qu''une fois les tokens complétés — au lieu de réanalyser depuis zéro à chaque fois. Le rapport avait juste besoin d''utiliser le même. Nous avions résolu exactement ce problème pour le chat des mois plus tôt ; le « correctif » pour le rapport a été de reconnaître que nous avions déjà l''outil et de le pointer vers une seconde surface. Réutiliser a battu reconstruire.

Le bonus : la copie enrichie est un problème de format de presse-papiers

Une fois que le rapport avait bonne allure, les gens ont voulu le coller — dans un document, un e-mail — en conservant la mise en forme et les captures d''écran. L''instinct est de sérialiser le rapport en une chaîne de markdown et d''espérer que la destination la rende. En général, elle ne le fait pas.

La vraie réponse, c''est que le presse-papiers contient plusieurs représentations à la fois. Nous en écrivons donc deux : une version HTML avec la mise en forme intacte et les captures intégrées en images en ligne, et une solution de repli en texte brut pour les destinations qui n''acceptent pas le HTML. Collez dans un éditeur enrichi et vous obtenez le rapport mis en forme avec les images ; collez dans une zone de texte brut et vous obtenez du texte propre. La « copie enrichie » n''a jamais été un problème de sérialisation — c''était un problème de choisir-le-bon-format-de-presse-papiers.

La même version a aussi donné au rapport des étiquettes de style chat Moi / Eux avec les deux interlocuteurs alignés sur des côtés opposés, pour qu''une transcription se lise comme la conversation qu''elle était, et a déplacé vos propres captures d''écran de leur côté pour correspondre. (Une version dans cette fenêtre, v1.6.14, était une pure recompilation — aucune histoire là-dedans, et il est honnête de le dire.)

Trois choses que l''interface en streaming nous a apprises

  1. Mettez en tampon les champs diffusés, publiez une fois par fragment. Laisser chaque server-sent event piloter sa propre mise à jour d''état, c''est le scintillement. Un accumulateur avec ref plus une unique publication par fragment, dans un arbre qui ne se démonte jamais, transforme l''assemblage nerveux en un remplissage apaisé.
  2. Tout ce qui se diffuse a besoin d''un moteur de rendu conscient du streaming. Un moteur qui réanalyse toute la chaîne à chaque token scintillera sur du markdown à moitié terminé. Utilisez-en un conçu pour les entrées incomplètes — et si vous en avez déjà un pour une autre surface, réutilisez-le avant d''en construire un second.
  3. La copie enrichie concerne les formats de presse-papiers, pas la sérialisation. Écrivez text/html avec des images intégrées et une solution de repli text/plain. Le presse-papiers a été conçu pour transporter les deux ; utilisez-le.

Ceci est le cinquième chapitre de l''histoire de fiabilité et de finition qui devient GeekBye v2. Pour le chapitre précédent, voir le bug des écouteurs (v1.6.12) ; pour l''endroit où la même idée de server-sent events est devenue plus tard tout un transport de repli, transcription en direct quand le pare-feu bloque les WebSockets (v2.0.8) ; et pour tout l''arc, l''anatomie de la livraison d''un logiciel jusqu''à la perfection.

Articles Connexes

Une connexion perdue ne devrait pas faire planter toute ton app — mais la nôtre le faisait
Steven
Steven7 min de lecture

Une connexion perdue ne devrait pas faire planter toute ton app — mais la nôtre le faisait

Quand notre backend est tombé en pleine réunion, il n'a pas seulement mis la transcription en pause — il a fait planter l'app entière. La cause était un unique événement non géré, et le correctif faisait dix lignes. Voici la grappe de versions qui a fait que GeekBye reste connecté à ton compte et reste connecté au réseau à travers les choses qui le tuaient avant.

Ingénierie
Fiabilité
Electron
Une variable CSS, cinq tours de revue et une chaîne d'outils Swift qui a menti
Steven
Steven8 min de lecture

Une variable CSS, cinq tours de revue et une chaîne d'outils Swift qui a menti

La v2.0.7 de GeekBye a rendu tout l'overlay translucide de façon ajustable — ce qui ressemble à une modification CSS d'une ligne et n'en était absolument pas une. La vraie histoire, c'est ce qu'a rattrapé la revue de code : deux surfaces qui refusaient de s'estomper, une barre d'enregistrement qui paraissait fausse à la même opacité, et un binaire compilé qui a oscillé de 672 octets parce que notre propre documentation a indiqué à un relecteur la mauvaise version de Swift.

Ingénierie
Design
Build
Le bug des écouteurs : quand l'autre personne a disparu de notre transcription
Steven
Steven7 min de lecture

Le bug des écouteurs : quand l'autre personne a disparu de notre transcription

La transcription à double canal fonctionnait parfaitement avec les haut-parleurs du portable et un casque circum-auriculaire. Puis quelqu'un a branché des écouteurs intra-auriculaires, et la personne à qui il parlait a tout simplement disparu de la transcription — parce que Chromium a coupé notre audio en silence à l'instant où il parlait. Onze correctifs ont échoué. Le douzième a changé toute l'architecture.

Ingénierie
Audio
Transcription