
Vi slettede 5.000 linjer lydkode — og transskriptioner begyndte at dukke op to gange
GeekBye v1.6 rev to Swift-transskribenter på selve enheden ud og erstattede dem med én samlet pipeline — en nettosletning på over 5.000 linjer, gjort mens folk sad midt i et møde i appen. Så begyndte transskriptioner at dukke op to gange, fordi fejlen flyttede til det ene lag, der stadig troede, der var to motorer.
Der findes en helt særlig form for rædsel i at refaktorere den kode, der kører lige nu, for folk der er midt i et møde. Du kan ikke tage funktionen ned til vedligeholdelse — nogen er afhængig af den for at transskribere en samtale, de ikke kan gentage. GeekBye v1.6 gjorde præcis dét ved den mest kritiske sti i appen: lyd-pipelinen. Dette er historien om, hvad vi slettede, hvad der gik i stykker, og de to lektioner om driftssikkerhed, der kom ud af det.
To motorer, den ene i Swift
Før v1.6 kørte GeekByes tale-til-tekst på selve enheden i Swift-binærfiler. Der var to af dem: en, der pakkede Apples Speech-framework ind, og en større (over 1.500 linjer), der fangede dobbelt lyd — din mikrofon plus systemlyd — via ScreenCaptureKit og åbnede sin egen WebSocket direkte til vores backend for at streame til Deepgram. To transskribenter, to fangststier, to måder at tale med netværket på, alt sammen i et kompileret sprog, der betød, at hver ændring krævede genkompilering af en binærfil.
v1.6.0 klappede det hele sammen til én pipeline. Den enkelte samlende commit slettede Apple Speech-transskribenten (~1.145 linjer), Deepgram Swift-fangeren (~1.523 linjer), begge TypeScript-broer, den gamle orkestrator og de dobbelt-lyd-IPC-handlere — en nettosletning på over 5.200 linjer. I deres sted: rendereren fanger lyd gennem standard Web APIs, sender den som PCM over IPC, og Electron-hovedprocessen ejer én enkelt WebSocket-sti til backenden, med udbyderen valgt af backend-konfigurationen. Én fangststi, én socket-ejer, ét sted at ræsonnere om.
At slette fem tusind linjer føles fantastisk. I omkring en dag.
Fejlen flyttede til det lag, der stadig troede, der var to
Så begyndte transskriptioner at dukke op to gange. En enkelt talt sætning ville blive gemt som to identiske poster — nogle gange værre.
Den grundlæggende årsag er det mest lærerige i hele denne release, fordi det er en generel lov om samling. Vi havde samlet udbyderne i hovedprocessen — én socket, én handler. Men React-laget havde ikke fået beskeden: det monterede stadig to udbyder-specifikke event-hooks på én gang, en bygget til Deepgram og en til ElevenLabs. Begge lyttede. Begge gemte. Hver transskription blev bevaret af hvilke hooks der end var i live — og det var de begge.
Og der var en anden, snedigere fordobling under det: ElevenLabs udsender hver færdiggjort linje to gange — én gang som en committed-event og igen som committed_with_timestamps. Så selv med en enkelt hook kunne én sætning ankomme som to events.
Løsningen (i v1.6-linjen) er en ryddelig todelt sag, der kortlægger præcist de to årsager: sæt en port foran hver event-hook bag et enabled-flag styret af backendens aktive-udbyder-konfiguration, så kun én lytter nogensinde er i live; og tilføj de-duplikering af tekst pr. kilde (husk den sidst gemte linje for mikrofon og for systemlyd) for at sluge ElevenLabs' dobbelt-udsendelse.
Lektionen er større end denne fejl: når du samler to backends bag én grænseflade, forsvinder duplikeringen ikke — den migrerer til det lag, der stadig antager, der er to. Vi klappede rørene sammen i hovedprocessen, og lækagen dukkede op to lag højere oppe, i brugerfladens event-forbindelser. At samle en pipeline er ikke færdigt, når producenten er samlet; det er færdigt, når hver forbruger er holdt op med at tro på den gamle form.
Den første genforbindelse: "giv op efter fem forsøg"
v1.6.0 leverede også GeekByes allerførste WebSocket-auto-genforbindelse med eksponentiel backoff — og den er værd at vise netop fordi den er den primitive stamfar til den skudsikre version, vi kører i dag.
Den oprindelige var begrænset. Fem forsøg, forsinkelser på 1s, 2s, 4s, 8s, 16s — og så overgav den sig, med en fatal fejl: "Forbindelse tabt — genstart venligst transskriptionen." På det tidspunkt føltes det ansvarligt: prøv ikke igen i evighed, fortæl brugeren det ærligt. I praksis var det en UX-fejl i forklædning. Et seksten-sekunders netværksudfald — et Wi-Fi-skift, en VPN-genforbindelse, en tunnel — er forbigående, men et begrænset genforsøg behandler det som endeligt. Brugeren gjorde intet forkert og fik at vide, at hun skulle genstarte et live møde.
Det er præcis den fejl, det nuværende design fjernede. I dag genopretter GeekBye forbindelsen med ubegrænset backoff og skifter endda udbydere, og stopper kun nogensinde af en genuint fatal grund — autentificering, kvote, fakturering. Hele rejsen fra "giv op efter fem" til "giv aldrig op ved en forbigående fejl" er fortalt i hvorfor dit AI-notatværktøj stopper ved dårligt Wi-Fi. v1.6 er der, hvor den vej begyndte, med den naive version, der måtte eksistere først.
Forældede forbindelser hjemsøger dig, medmindre de har navne
Endnu en lektion om driftssikkerhed landede i v1.6.3 og rettede en subtil klasse af fejl, som enhver, der laver genforbindelse over en tilstandsbevidst backend, i sidste ende vil møde.
Når du genopretter forbindelsen — eller skifter transskriptionssprog midt i en session, hvilket genopretter forbindelsen under motorhjelmen — dør den gamle forbindelse ikke altid stille. Dens dødsrallen-beskeder (CONNECTION_LOST, disconnected) kan ankomme efter at den nye forbindelse allerede er rask, og rive den helt fine afløser ned. Løsningen giver hvert forbindelsesforsøg en identitet — et session-id pr. forbindelse stemplet på WebSocket-URL'en — plus et kort nådevindue efter genforbindelse, hvor forældede frakoblingsbeskeder fra et tidligere forsøg ignoreres. Den sender også en eksplicit stop-besked, før en socket lukkes, så backenden kan rive den gamle session rent ned.
Princippet: enhver genforbindelseslogik over en tilstandsbevidst backend skal mærke hvert forsøg med en identitet og behandle sene beskeder fra tidligere forsøg som støj — ellers bliver en vellykket genforbindelse dræbt af sin forgængers døende åndedrag.
Tre ting v1.6 lærte os
- Samling flytter fejl; den sletter dem ikke. Klap to motorer sammen til én, og gennemgå hver forbruger, der stadig tror, der er to. Vores var brugerfladen, der monterede to event-hooks, efter at rørene allerede var flettet sammen.
- Din første genforbindelse vil være begrænset, og begrænset er forkert for live streams. "Giv op efter N forsøg" forvandler et forbigående udfald til en endelig fejl, brugeren får skylden for. Skeln mellem fatal (autentificering, kvote), forbigående (en tabt socket) fra allerførste version.
- Genforbindelser har brug for identitet. Mærk hvert forsøg; ignorér de sene beskeder fra døde forsøg. En forbindelse uden navn kan blive myrdet af sit eget spøgelse.
Dette er andet kapitel i den fortælling om driftssikkerhed, der bliver til GeekBye v2. For det første, se auto-opdaterings-sagaen, der tog seks releases på fire dage (v1.5.x); for hvor genforbindelsesarbejdet til sidst nåede hen, hvorfor dit AI-notatværktøj stopper ved dårligt Wi-Fi; og for hele buen, anatomien af at skibe software til perfektion.