Steven
Steven6 menit baca

Kami Menghapus 5.000 Baris Kode Audio — dan Transkrip Mulai Muncul Dua Kali

GeekBye v1.6 mencabut dua transkriber Swift di perangkat dan menggantinya dengan satu pipeline terpadu — penghapusan bersih lebih dari 5.000 baris, dilakukan saat orang sedang di tengah rapat di aplikasi. Lalu transkrip mulai muncul dua kali, karena bug itu berpindah ke satu lapisan yang masih mengira ada dua mesin.

Rekayasa
Transkripsi
Arsitektur
GeekBye Releases
Kami Menghapus 5.000 Baris Kode Audio — dan Transkrip Mulai Muncul Dua Kali

Ada jenis kengerian tertentu dalam me-refactor kode yang sedang berjalan saat ini juga, untuk orang yang sedang di tengah rapat. Anda tidak bisa menonaktifkan fitur untuk pemeliharaan — seseorang mengandalkannya untuk mentranskripsikan percakapan yang tidak bisa mereka ulangi. GeekBye v1.6 melakukan persis itu pada jalur paling kritis di aplikasi: pipeline audio. Ini adalah kisah tentang apa yang kami hapus, apa yang rusak, dan dua pelajaran keandalan yang muncul darinya.

Dua mesin, salah satunya di Swift

Sebelum v1.6, speech-to-text GeekBye berjalan di perangkat dalam biner Swift. Ada dua di antaranya: satu membungkus framework Speech milik Apple, dan yang lebih besar (lebih dari 1.500 baris) yang menangkap audio ganda — mikrofon Anda plus audio sistem — lewat ScreenCaptureKit dan membuka WebSocket-nya sendiri langsung ke backend kami untuk streaming ke Deepgram. Dua transkriber, dua jalur penangkapan, dua cara berbicara dengan jaringan, semuanya dalam bahasa terkompilasi yang berarti setiap perubahan memerlukan kompilasi ulang sebuah biner.

v1.6.0 meruntuhkan semuanya menjadi satu pipeline. Commit penyatu tunggal itu menghapus transkriber Apple Speech (~1.145 baris), penangkap Swift Deepgram (~1.523 baris), kedua jembatan TypeScript, orkestrator lama, dan handler IPC audio ganda — penghapusan bersih lebih dari 5.200 baris. Sebagai gantinya: renderer menangkap audio lewat Web APIs standar, mengirimkannya sebagai PCM lewat IPC, dan proses utama Electron memiliki satu jalur WebSocket ke backend, dengan penyedia dipilih oleh konfigurasi backend. Satu jalur penangkapan, satu pemilik socket, satu tempat untuk bernalar.

Menghapus lima ribu baris terasa luar biasa. Selama sekitar satu hari.

Bug itu berpindah ke lapisan yang masih mengira ada dua

Lalu transkrip mulai muncul dua kali. Satu kalimat yang diucapkan akan disimpan sebagai dua entri identik — kadang lebih buruk.

Akar penyebabnya adalah hal paling instruktif dalam seluruh rilis ini, karena ini adalah hukum umum penyatuan. Kami sudah menyatukan penyedia di proses utama — satu socket, satu handler. Tetapi lapisan React belum mendapat memonya: ia masih memasang dua hook peristiwa spesifik penyedia sekaligus, satu dibangun untuk Deepgram dan satu untuk ElevenLabs. Keduanya mendengarkan. Keduanya menyimpan. Setiap transkrip dipersistensikan oleh hook mana pun yang hidup — dan keduanya hidup.

Dan ada penggandaan kedua yang lebih licik di bawahnya: ElevenLabs memancarkan setiap baris final dua kali — sekali sebagai peristiwa committed dan sekali lagi sebagai committed_with_timestamps. Jadi bahkan dengan satu hook, satu kalimat bisa tiba sebagai dua peristiwa.

Perbaikannya (di jalur v1.6) adalah dwilogi rapi yang persis memetakan kedua penyebab: pagari setiap hook peristiwa di balik flag enabled yang digerakkan oleh konfigurasi penyedia aktif backend, sehingga hanya satu pendengar yang pernah hidup; dan tambahkan deduplikasi teks per sumber (ingat baris terakhir yang disimpan untuk mikrofon dan untuk audio sistem) untuk menelan pancaran ganda ElevenLabs.

Pelajarannya lebih besar dari bug ini: ketika Anda menyatukan dua backend di balik satu antarmuka, duplikasi tidak lenyap — ia bermigrasi ke lapisan yang masih menganggap ada dua. Kami meruntuhkan pipa di proses utama dan kebocorannya muncul dua lapisan di atas, di penyambungan peristiwa UI. Menyatukan sebuah pipeline belum selesai ketika produsen sudah disatukan; selesai ketika setiap konsumen berhenti memercayai bentuk lama.

Reconnect pertama: "menyerah setelah lima percobaan"

v1.6.0 juga mengirimkan reconnect otomatis WebSocket pertama GeekBye dengan exponential backoff — dan layak ditunjukkan justru karena ini adalah leluhur primitif dari versi antipeluru yang kami jalankan hari ini.

Yang asli itu terbatas. Lima percobaan, dengan jeda 1s, 2s, 4s, 8s, 16s — lalu ia menyerah, memancarkan galat fatal: "Koneksi terputus — silakan mulai ulang transkripsi." Saat itu terasa bertanggung jawab: jangan mencoba selamanya, beri tahu pengguna dengan jujur. Dalam praktiknya itu adalah bug UX yang menyamar. Gangguan jaringan enam belas detik — perpindahan Wi-Fi, reconnect VPN, sebuah terowongan — bersifat sementara, tetapi retry terbatas memperlakukannya sebagai terminal. Pengguna tidak melakukan kesalahan apa pun dan disuruh memulai ulang rapat yang sedang berlangsung.

Itulah persis kegagalan yang dihilangkan oleh desain saat ini. Hari ini GeekBye reconnect dengan backoff tak terbatas dan bahkan menggilir penyedia, dan hanya berhenti untuk alasan yang benar-benar fatal — auth, kuota, penagihan. Seluruh perjalanan dari "menyerah setelah lima" ke "jangan pernah menyerah pada galat sementara" diceritakan di mengapa pencatat AI Anda berhenti saat Wi-Fi buruk. v1.6 adalah tempat jalan itu dimulai, dengan versi naif yang harus ada lebih dulu.

Koneksi basi menghantui Anda kecuali mereka punya nama

Satu pelajaran keandalan lagi mendarat di v1.6.3, memperbaiki kelas bug halus yang cepat atau lambat akan ditemui siapa pun yang melakukan reconnect di atas backend berstatus.

Ketika Anda reconnect — atau mengganti bahasa transkripsi di tengah sesi, yang reconnect di balik layar — koneksi lama tidak selalu mati dengan tenang. Pesan sekaratnya (CONNECTION_LOST, disconnected) bisa tiba setelah koneksi baru sudah sehat, dan merobohkan pengganti yang sudah sempurna baik. Perbaikannya memberi setiap upaya koneksi sebuah identitas — ID sesi per koneksi yang dicap pada URL WebSocket — plus jendela toleransi singkat setelah reconnect yang selama itu pesan diskonek basi dari upaya sebelumnya diabaikan. Ia juga mengirim pesan stop eksplisit sebelum menutup socket agar backend bisa merobohkan sesi lama dengan bersih.

Prinsipnya: logika reconnect apa pun di atas backend berstatus perlu menandai setiap upaya dengan identitas dan memperlakukan pesan terlambat dari upaya sebelumnya sebagai derau — jika tidak, reconnect yang berhasil akan dibunuh oleh napas terakhir pendahulunya.

Tiga hal yang diajarkan v1.6

  1. Penyatuan memindahkan bug; bukan menghapusnya. Runtuhkan dua mesin menjadi satu dan audit setiap konsumen yang masih mengira ada dua. Milik kami adalah UI, yang memasang dua hook peristiwa setelah pipanya sudah menyatu.
  2. Reconnect pertama Anda akan terbatas, dan terbatas itu salah untuk aliran langsung. "Menyerah setelah N percobaan" mengubah gangguan sementara menjadi kegagalan terminal yang justru disalahkan pada pengguna. Bedakan fatal (auth, kuota) dari sementara (socket yang putus) sejak versi pertama.
  3. Reconnect butuh identitas. Tandai setiap upaya; abaikan pesan terlambat dari upaya yang sudah mati. Koneksi tanpa nama bisa dibunuh oleh hantunya sendiri.

Ini adalah bab kedua dari kisah keandalan yang menjadi GeekBye v2. Untuk yang pertama, lihat saga auto-update yang memakan enam rilis dalam empat hari (v1.5.x); untuk ke mana pekerjaan reconnect akhirnya tiba, mengapa pencatat AI Anda berhenti saat Wi-Fi buruk; dan untuk keseluruhan busurnya, anatomi merilis perangkat lunak hingga sempurna.