Steven
Steven7 min de lectura

Vam esborrar 5.000 línies de codi d'àudio — i les transcripcions van començar a aparèixer duplicades

GeekBye v1.6 va arrencar dos transcriptors Swift al dispositiu i els va substituir per una única canonada unificada — una eliminació neta de més de 5.000 línies, feta mentre la gent era enmig d'una reunió a l'aplicació. Llavors les transcripcions van començar a aparèixer dues vegades, perquè l'error va migrar a l'única capa que encara pensava que hi havia dos motors.

Enginyeria
Transcripció
Arquitectura
GeekBye Releases
Vam esborrar 5.000 línies de codi d'àudio — i les transcripcions van començar a aparèixer duplicades

Hi ha una mena de terror particular en refactoritzar el codi que s''està executant ara mateix, per a gent que és enmig d''una reunió. No pots deixar la funció fora de servei per manteniment — algú hi confia per transcriure una conversa que no pot repetir. GeekBye v1.6 va fer exactament això amb el camí més crític de l''aplicació: la canonada d''àudio. Aquesta és la història del que vam esborrar, del que es va trencar, i de les dues lliçons de fiabilitat que en van sortir.

Dos motors, un d''ells en Swift

Abans de la v1.6, la conversió de veu a text de GeekBye s''executava al dispositiu en binaris Swift. N''hi havia dos: un que embolcallava el framework Speech d''Apple, i un de més gran (més de 1.500 línies) que capturava àudio dual — el teu micròfon més l''àudio del sistema — mitjançant ScreenCaptureKit i obria el seu propi WebSocket directament al nostre backend per fer streaming cap a Deepgram. Dos transcriptors, dos camins de captura, dues maneres de parlar amb la xarxa, tot en un llenguatge compilat que significava que cada canvi requeria recompilar un binari.

v1.6.0 ho va col·lapsar tot en una única canonada. El commit unificador únic va eliminar el transcriptor Apple Speech (~1.145 línies), el capturador Swift de Deepgram (~1.523 línies), tots dos ponts de TypeScript, l''antic orquestrador i els gestors IPC d''àudio dual — una eliminació neta de més de 5.200 línies. Al seu lloc: el renderitzador captura l''àudio mitjançant Web APIs estàndard, l''envia com a PCM per IPC, i el procés principal d''Electron és propietari d''un únic camí WebSocket cap al backend, amb el proveïdor triat per la configuració del backend. Un camí de captura, un propietari de socket, un lloc on raonar.

Esborrar cinc mil línies fa una sensació genial. Durant un dia, més o menys.

L''error va migrar a la capa que encara pensava que n''hi havia dos

Llavors les transcripcions van començar a aparèixer duplicades. Una sola frase pronunciada es desava com a dues entrades idèntiques — de vegades pitjor.

La causa arrel és la cosa més instructiva de tota aquesta versió, perquè és una llei general de la unificació. Havíem unificat els proveïdors al procés principal — un socket, un gestor. Però la capa de React no havia rebut el missatge: encara muntava dos ganxos d''esdeveniments específics de cada proveïdor alhora, un construït per a Deepgram i un per a ElevenLabs. Tots dos escoltaven. Tots dos desaven. Cada transcripció es persistia pel ganxo que estigués viu — i tots dos ho estaven.

I hi havia una segona duplicació, més sibil·lina, a sota: ElevenLabs emet cada línia finalitzada dues vegades — una com a esdeveniment committed i una altra com a committed_with_timestamps. Així que fins i tot amb un únic ganxo, una frase podia arribar com a dos esdeveniments.

La solució (a la línia v1.6) és un díptic polit que encaixa exactament amb les dues causes: bloqueja cada ganxo d''esdeveniments darrere d''un indicador enabled impulsat per la configuració del proveïdor actiu del backend, de manera que només hi hagi mai un oient viu; i afegeix desduplicació de text per font (recorda l''última línia desada per al micròfon i per a l''àudio del sistema) per empassar la doble emissió d''ElevenLabs.

La lliçó és més gran que aquest error: quan unifiques dos backends darrere d''una interfície, la duplicació no desapareix — migra a la capa que encara assumeix que n''hi ha dos. Vam col·lapsar la fontaneria al procés principal i la fuita va aflorar dues capes més amunt, al cablejat d''esdeveniments de la interfície. Unificar una canonada no està acabat quan el productor està unificat; està acabat quan cada consumidor ha deixat de creure en la forma antiga.

La primera reconnexió: "rendeix-te després de cinc intents"

La v1.6.0 també va llançar la primera reconnexió automàtica WebSocket de GeekBye amb backoff exponencial — i val la pena mostrar-la precisament perquè és l''avantpassat primitiu de la versió a prova de bales que executem avui.

L''original era limitat. Cinc intents, amb retards d''1s, 2s, 4s, 8s, 16s — i llavors es rendia, emetent un error fatal: "Connexió perduda — reinicia la transcripció, si us plau." En aquell moment això semblava responsable: no reintentis per sempre, digues-ho honestament a l''usuari. A la pràctica era un error d''UX disfressat. Una interrupció de xarxa de setze segons — un traspàs de Wi-Fi, una reconnexió de VPN, un túnel — és transitòria, però un reintent limitat la tracta com a terminal. L''usuari no va fer res malament i se li deia que reiniciés una reunió en directe.

Aquesta és exactament la fallada que el disseny actual va eliminar. Avui GeekBye reconnecta amb backoff il·limitat i fins i tot alterna proveïdors, i només s''atura mai per una raó genuïnament fatal — auth, quota, facturació. Tot el viatge de "rendeix-te després de cinc" a "no et rendeixis mai davant d''un error transitori" s''explica a per què el teu prenedor de notes amb IA s''atura amb mala Wi-Fi. La v1.6 és on va començar aquest camí, amb la versió ingènua que havia d''existir primer.

Les connexions obsoletes t''empaiten tret que tinguin noms

Una lliçó de fiabilitat més va aterrar a la v1.6.3, corregint una classe subtil d''error que qualsevol que faci reconnexió sobre un backend amb estat trobarà tard o d''hora.

Quan reconnectes — o canvies l''idioma de transcripció enmig d''una sessió, cosa que reconnecta per sota — la connexió antiga no sempre mor silenciosament. Els seus missatges d''estertor (CONNECTION_LOST, disconnected) poden arribar després que la nova connexió ja estigui sana, i enderrocar el reemplaçament perfectament bo. La solució dona a cada intent de connexió una identitat — un ID de sessió per connexió estampat a l''URL del WebSocket — més una breu finestra de gràcia després de la reconnexió durant la qual s''ignoren els missatges de desconnexió obsolets d''un intent anterior. També envia un missatge stop explícit abans de tancar un socket perquè el backend pugui enderrocar l''antiga sessió netament.

El principi: qualsevol lògica de reconnexió sobre un backend amb estat necessita etiquetar cada intent amb una identitat i tractar els missatges tardans d''intents anteriors com a soroll — altrament una reconnexió reeixida acaba morta per l''últim alè del seu predecessor.

Tres coses que la v1.6 va ensenyar

  1. La unificació reubica els errors; no els elimina. Col·lapsa dos motors en un i audita cada consumidor que encara pensi que n''hi ha dos. El nostre era la interfície, muntant dos ganxos d''esdeveniments després que la fontaneria ja s''hagués fusionat.
  2. La teva primera reconnexió serà limitada, i limitada és incorrecte per als fluxos en directe. "Rendeix-te després de N intents" converteix una interrupció transitòria en una fallada terminal de la qual es culpa l''usuari. Distingeix fatal (auth, quota) de transitori (un socket caigut) des de la primera versió.
  3. Les reconnexions necessiten identitat. Etiqueta cada intent; ignora els missatges tardans dels intents morts. Una connexió sense nom pot ser assassinada pel seu propi fantasma.

Aquest és el segon capítol de la història de fiabilitat que esdevé GeekBye v2. Per al primer, mira la saga de l''actualització automàtica que va costar sis versions en quatre dies (v1.5.x); per a on va arribar finalment el treball de reconnexió, per què el teu prenedor de notes amb IA s''atura amb mala Wi-Fi; i per a tot l''arc, l''anatomia de llançar programari fins a la perfecció.

Articles Relacionats

Una connexió perduda no hauria de fer petar tota l'aplicació — però la nostra ho feia
Steven
Steven7 min de lectura

Una connexió perduda no hauria de fer petar tota l'aplicació — però la nostra ho feia

Quan el nostre backend es va desconnectar en plena reunió, no només va aturar la transcripció: va fer petar tota l'aplicació. La causa era un únic esdeveniment sense gestionar, i la solució eren deu línies. Aquest és el grup de versions que va fer que GeekBye es mantingués amb la sessió iniciada i connectat a través de les coses que abans el mataven.

Enginyeria
Fiabilitat
Electron
La teva app de Mac obté accés al micròfon — i l'oblida a cada arrencada
Steven
Steven6 min de lectura

La teva app de Mac obté accés al micròfon — i l'oblida a cada arrencada

GeekBye va demanar permís per al micròfon, li'l vas donar i funcionava. A la següent arrencada: desaparegut. I l'app no apareixia enlloc a Configuració del Sistema → Micròfon. El culpable era una funció de seguretat de macOS que executava l'app en silenci des d'una ruta que desapareix — aquí tens el diagnòstic i la solució d'un sol avís.

macOS
Permisos
Enginyeria
La comprovació de seguretat que va fer impossible tancar la nostra aplicació
Steven
Steven6 min de lectura

La comprovació de seguretat que va fer impossible tancar la nostra aplicació

L'actualització automàtica va ser la funció més difícil que hem publicat mai — sis versions en quatre dies per evitar que deixés l'aplicació inservible. El pitjor error va ser un que vam introduir nosaltres mateixos intentant anar amb compte: una comprovació de "seguretat" de 500 mil·lisegons que convertia una actualització fallida en un procés que literalment no podies tancar.

Enginyeria
Electron
Fiabilitat