Steven
Steven6 min čtení

Smazali Jsme 5 000 Řádků Zvukového Kódu — a Přepisy Se Začaly Zobrazovat Dvakrát

GeekBye v1.6 vytrhlo dva zařízení běžící Swift transkriptory a nahradilo je jedním sjednoceným řetězcem — čistý úbytek přes 5 000 řádků, provedený zatímco lidé byli uprostřed schůzky přímo v aplikaci. Pak se přepisy začaly objevovat dvakrát, protože chyba se přesunula do jediné vrstvy, která si pořád myslela, že existují dva engine.

Inženýrství
Přepis
Architektura
Vydání GeekBye
Smazali Jsme 5 000 Řádků Zvukového Kódu — a Přepisy Se Začaly Zobrazovat Dvakrát

Existuje zvláštní druh hrůzy v refaktorování kódu, který běží právě teď, pro lidi, kteří jsou uprostřed schůzky. Nemůžeš funkci vypnout kvůli údržbě — někdo se na ni spoléhá, aby přepsala rozhovor, který nelze zopakovat. GeekBye v1.6 udělalo přesně tohle nejkritičtější cestě v aplikaci: zvukovému řetězci. Tohle je příběh o tom, co jsme smazali, co se rozbilo, a o dvou lekcích spolehlivosti, které z toho vzešly.

Dva engine, jeden z nich ve Swiftu

Před v1.6 běžel převod řeči na text v GeekBye na zařízení ve Swift binárkách. Byly dvě: jedna obalovala Apple Speech framework, a větší (přes 1,500 řádků) zachytávala duální zvuk — tvůj mikrofon plus systémový zvuk — přes ScreenCaptureKit a otevírala si vlastní WebSocket rovnou na náš backend, aby streamovala do Deepgramu. Dva transkriptory, dvě cesty zachytávání, dva způsoby komunikace se sítí, vše v kompilovaném jazyce, což znamenalo, že každá změna vyžadovala rekompilaci binárky.

v1.6.0 to všechno sloučilo do jednoho řetězce. Jediný sjednocující commit smazal Apple Speech transkriptor (~1,145 řádků), Deepgram Swift zachytávač (~1,523 řádků), oba TypeScript můstky, starý orchestrátor a IPC handlery pro duální zvuk — čistý úbytek přes 5,200 řádků. Na jejich místě: renderer zachytává zvuk přes standardní Web APIs, posílá ho jako PCM přes IPC, a hlavní proces Electronu vlastní jedinou WebSocket cestu na backend, přičemž poskytovatele volí konfigurace backendu. Jedna cesta zachytávání, jeden vlastník socketu, jedno místo, kde o tom přemýšlet.

Smazat pět tisíc řádků je skvělý pocit. Zhruba na den.

Chyba se přesunula do vrstvy, která si pořád myslela, že jsou dva

Pak se přepisy začaly objevovat dvakrát. Jediná vyřčená věta se uložila jako dva identické záznamy — někdy hůř.

Kořenová příčina je to nejpoučnější v celém tomto vydání, protože je to obecný zákon sjednocování. Sjednotili jsme poskytovatele v hlavním procesu — jeden socket, jeden handler. Ale React vrstva to nepochopila: pořád montovala dva event hooky specifické pro poskytovatele naráz, jeden postavený pro Deepgram a jeden pro ElevenLabs. Oba naslouchaly. Oba ukládaly. Každý přepis byl uložen tím, který hook byl naživu — a naživu byly oba.

A pod tím vším bylo druhé, záludnější zdvojení: ElevenLabs vysílá každý finalizovaný řádek dvakrát — jednou jako committed událost a znovu jako committed_with_timestamps. Takže i s jediným hookem mohla jedna věta dorazit jako dvě události.

Oprava (v řadě v1.6) je úhledná dvoudílná záležitost, která přesně mapuje na dvě příčiny: uzavři každý event hook za enabled příznak řízený konfigurací aktivního poskytovatele z backendu, takže naživu je vždy jen jeden posluchač; a přidej deduplikaci textu podle zdroje (zapamatuj si poslední uložený řádek pro mikrofon a pro systémový zvuk), abys spolkl dvojité vysílání ElevenLabs.

Lekce je větší než tato chyba: když sjednotíš dva backendy za jedno rozhraní, duplikace nezmizí — přestěhuje se do vrstvy, která pořád předpokládá, že jsou dvě. Sloučili jsme instalatérské trubky v hlavním procesu a únik se vynořil o dvě vrstvy výš, v propojení událostí v UI. Sjednocení řetězce není hotové, když je sjednocen producent; je hotové, když každý konzument přestal věřit ve starý tvar.

První opětovné připojení: „vzdej to po pěti pokusech"

v1.6.0 také přineslo úplně první automatické opětovné připojení WebSocketu v GeekBye s exponenciálním backoffem — a stojí za to ho ukázat právě proto, že je to primitivní předek neprůstřelné verze, kterou provozujeme dnes.

Původní verze byla omezená. Pět pokusů, prodlevy 1s, 2s, 4s, 8s, 16s — a pak to vzdala, vyslala fatální chybu: „Připojení ztraceno — restartujte prosím přepis." Tehdy to působilo zodpovědně: neopakuj donekonečna, řekni uživateli poctivě. V praxi to byla skrytá UX chyba. Šestnáctisekundový síťový výpadek — přepnutí Wi-Fi, opětovné připojení VPN, tunel — je přechodný, ale omezené opakování s ním zachází jako s konečným. Uživatel neudělal nic špatně a dostal pokyn restartovat živou schůzku.

To je přesně to selhání, které současný návrh odstranil. Dnes se GeekBye opětovně připojuje s neomezeným backoffem a dokonce střídá poskytovatele, a zastaví se jen z opravdu fatálního důvodu — autentizace, kvóta, fakturace. Celá cesta od „vzdej to po pěti" k „nikdy se nevzdávej při přechodné chybě" je vyprávěna v proč se váš AI zapisovač poznámek zastaví na špatné Wi-Fi. v1.6 je tam, kde tato cesta začala, s naivní verzí, která musela existovat jako první.

Zastaralá připojení tě pronásledují, dokud nemají jména

Ještě jedna lekce spolehlivosti přistála v v1.6.3 a opravila záludnou třídu chyb, se kterou se nakonec potká každý, kdo dělá opětovné připojení přes stavový backend.

Když se opětovně připojíš — nebo přepneš jazyk přepisu uprostřed relace, což se pod kapotou opětovně připojí — staré připojení ne vždy zemře tiše. Jeho zprávy na smrtelné posteli (CONNECTION_LOST, disconnected) mohou dorazit poté, co je nové připojení už zdravé, a strhnout tuhle naprosto dobrou náhradu. Oprava dává každému pokusu o připojení identitu — ID relace pro každé připojení otištěné do WebSocket URL — plus krátkou ochrannou lhůtu po opětovném připojení, během které se zastaralé zprávy o odpojení z předchozího pokusu ignorují. Také posílá explicitní stop zprávu před uzavřením socketu, aby backend mohl starou relaci čistě strhnout.

Princip: jakákoliv logika opětovného připojení přes stavový backend musí označit každý pokus identitou a zacházet s pozdními zprávami z předchozích pokusů jako se šumem — jinak úspěšné opětovné připojení zabije poslední výdech jeho předchůdce.

Tři věci, které nás v1.6 naučilo

  1. Sjednocení chyby přemisťuje; nemaže je. Sluč dva engine do jednoho a prověř každého konzumenta, který si pořád myslí, že jsou dva. Ten náš bylo UI, montující dva event hooky poté, co se instalatérské trubky už spojily.
  2. Tvé první opětovné připojení bude omezené, a omezené je pro živé streamy špatně. „Vzdej to po N pokusech" mění přechodný výpadek na konečné selhání, ze kterého je viněn uživatel. Rozlišuj fatální (autentizace, kvóta) od přechodného (spadlý socket) hned od první verze.
  3. Opětovná připojení potřebují identitu. Označ každý pokus; ignoruj pozdní zprávy z mrtvých pokusů. Připojení bez jména může být zavražděno svým vlastním duchem.

Tohle je druhá kapitola příběhu o spolehlivosti, který se stává GeekBye v2. Pro tu první viz sága automatických aktualizací, která zabrala šest vydání za čtyři dny (v1.5.x); pro to, kam práce na opětovném připojení nakonec dorazila, proč se váš AI zapisovač poznámek zastaví na špatné Wi-Fi; a pro celý oblouk anatomie dodávání softwaru k dokonalosti.

Související články

Přerušené spojení by nemělo shodit celou aplikaci — ale ta naše to dělala
Steven
Steven6 min čtení

Přerušené spojení by nemělo shodit celou aplikaci — ale ta naše to dělala

Když nám backend spadl uprostřed schůzky, nezastavil jen přepis — shodil celou aplikaci. Příčinou byla jediná neošetřená událost a oprava měla deset řádků. Tohle je shluk vydání, díky kterému GeekBye zůstává přihlášený a připojený i skrz věci, které ho dřív zabíjely.

Inženýrství
Spolehlivost
Electron
Vaše Mac aplikace získá přístup k mikrofonu — a pak ho při každém spuštění zapomene
Steven
Steven5 min čtení

Vaše Mac aplikace získá přístup k mikrofonu — a pak ho při každém spuštění zapomene

GeekBye požádal o oprávnění k mikrofonu, udělili jste ho a fungovalo to. Při dalším spuštění: pryč. A aplikace se v Nastavení systému → Mikrofon vůbec neobjevila. Viníkem byla bezpečnostní funkce macOS, která aplikaci tiše spouštěla z cesty, jež mizí — tady je diagnóza a oprava pomocí jediné výzvy.

macOS
Oprávnění
Inženýrství
Jedna proměnná CSS, pět kol revize a Swift toolchain, který lhal
Steven
Steven6 min čtení

Jedna proměnná CSS, pět kol revize a Swift toolchain, který lhal

GeekBye v2.0.7 udělal celý overlay nastavitelně průsvitným — což zní jako jednořádková změna v CSS a rozhodně nebylo. Skutečný příběh je to, co odhalila revize kódu: dvě plochy, které odmítaly zprůhlednět, nahrávací lišta, jež vypadala špatně při stejné neprůhlednosti, a zkompilovaná binárka, která poskočila o 672 bajtů, protože naše vlastní dokumentace řekla revidujícímu špatnou verzi Swiftu.

Inženýrství
Design
Build