In aprilie am scris despre o saptamana in care apelurile ba mergeau, ba omorau clientul, iar Xcode se certa cu iCloud de parca era jobul lui principal. Dupa putin peste o luna, GChat nu mai pare un build pentru patru buzunare. Prietenii chiar stau in chat-uri, suna iPhone ↔ Android, trimit bug-uri, iar bug-urile au devenit adulte.
Inainte, o plangere suna cam asa: “ceva nu e in regula cu sunetul”. Acum suna mai des asa: “pe iPhone aud tonul de apel, Android a acceptat, dar nu se aude vocea”, “dupa acceptare, ringtone-ul sistemului nu s-a oprit”, “fara internet aplicatia se poarta ca si cum nu sunt logat”. Asta nu mai e ceata. Asta e material de investigatie.
Mai jos este ce s-a schimbat intre 27 aprilie si build-ul curent 0.9.106 pe canalul pre-release. Nu comunicat de presa, ci jurnal de inginerie: ce s-a reparat, ce a trebuit rescris si ce inca doare cinstit.
De la beta la pre-release
Din exterior, schimbarea e mica, dar seteaza asteptari. Tab-ul “Protectie” a devenit “Setari”, scutul a fost inlocuit cu o rotita, in footer scrie pre-release in loc de pre-beta, iar support@gchat.tech deschide clientul de email la tap.
Tot nu este release in store, dar nici “ne jucam putin” nu mai este. Produsul traieste la oameni, deci oamenii trebuie sa aiba un mod clar de a spune: “aici s-a rupt”.
Mac nu mai este un telefon pe ecran mare
Cea mai mare schimbare din mai a fost suportul multi-device real.
Inainte, logica era mai aproape de “telefonul e principal, restul sta langa el”. Acum fiecare dispozitiv este propria identitate criptografica: propriile chei, propriile sesiuni de criptare, propriul device_id. Cand trimit un mesaj cuiva care are iPhone si Mac, serverul nu primeste un text comun si nu imparte copii. Clientul cripteaza din start un payload separat pentru fiecare dispozitiv al destinatarului.
Pe scurt: Mac nu mai este oglinda telefonului. Este participant independent la conversatie.
Pentru autentificare pe Mac exista doua cai normale:
- Recovery Phrase — restaurare printr-o fraza care decripteaza identity material.
- Phone Export — pairing prin QR de pe telefon, dupa care Mac traieste singur.
Telefonul si Mac-ul pot functiona simultan. E mai aproape de Signal decat de “deschide versiunea web si scaneaza QR in fiecare seara”.
Costul a fost previzibil: trimiterea si primirea pe mobil au trebuit rescrise, deduplicarea a trebuit stransa, iar decriptarea trebuia sa functioneze cand interlocutorul are doua dispozitive active. Am prins si o regresie cinstita: mesajele intre telefoane nu se mai decriptau. Prioritatea a mers imediat acolo, pentru ca E2E fara decriptare nu e messenger. E o forma scumpa de tacere.
Separat, aplicatia iOS rulata pe macOS prin “iPhone apps on Mac” afiseaza acum un placeholder full-screen. Versiunea macOS va fi client separat, nu un hack peste build-ul de iPad.
Profiluri si Oameni nu mai sunt doar contacte
Mult timp, profilul din GChat era aproape o fisa tehnica: avatar si @handle. In mai a inceput sa semene cu un strat social real.
Au aparut:
- bio in setari si in cardul de profil;
- istoric de avatare, unde pozele vechi nu dispar dupa schimbarea celei principale;
- tab-ul Oameni cu poze, @tag, ultima activitate, verificari si stories;
- deschiderea profilului prin tap pe avatarul din header-ul unui chat 1:1;
- actiuni rapide “scrie” si “suna” din profil.
Backend-ul a primit migrari pentru bio si istoricul de avatare. Pe hartie suna ca strat UI, dar senzatia conteaza mai mult. GChat inceteaza sa fie “chat-uri cu criptografie” si devine o aplicatie in care oamenii au prezenta. Criptarea ramane sub capota, dar utilizatorul nu trebuie sa-si aminteasca in fiecare minut ca sta intr-un experiment ingineresc.
Chat: mai putin “merge”, mai mult “poti trai aici”
In paralel cu profilurile au intrat multe detalii de folosire zilnica:
- buton de scroll jos;
- data plutitoare la scroll;
- micro-animatii si haptic feedback;
- selectie multipla de mesaje;
- cautare locala in chat;
- preview in reply;
- editare de mesaje fara a rupe protocolul E2E;
- optimizare pentru preview-uri foto si baza pentru thumbnail-cache.
O parte este plictisitoare si tocmai de aceea importanta: arhiva SQLite pentru mesaje vechi, index local, media cache, backend transit-only. Serverul nu stocheaza istoricul chat-urilor; se ocupa de livrare. Pentru confidentialitate e bine, dar responsabilitatea se muta in client: cautarea, cache-ul, arhiva si restaurarea starii trebuie sa functioneze pe dispozitiv.
Aici un messenger devine matur nu printr-o functie spectaculoasa, ci prin suma lucrurilor mici. Cand data nu sare, pozele nu mananca memoria, cautarea gaseste o fraza veche, iar reply-ul arata preview, creierul nu mai observa interfata. Asta e tinta.
Apeluri: de la “poate e reteaua” la diagnostic
In postarea din aprilie, durerea era bruta: apelurile puteau crapa clientul din cauza flutter_webrtc. In mai problema a devenit mai fina. Aplicatia nu mai crapa, dar calitatea si rutarea audio puteau inca sa se comporte ciudat:
- iPhone suna Android, tonul merge, dar vocea nu se aude;
- pe video call, speakerphone apare activ, dar sunetul ramane in casca;
- CallKit continua sa cante ringtone-ul sistemului chiar dupa ce utilizatorul accepta apelul in aplicatie.
Investigatia a ajuns in intersectia dintre AVAudioSession, sunetul de ringback si flutter_webrtc. Fara magie specifica Apple: iOS pazeste audio-ul foarte atent. Cat timp aplicatia reda tonul de apel, sistemul poate tine un mod audio; cand WebRTC vrea microfonul si difuzorul, aplicatia trebuie sa confirme din nou ca acesta este acum apel VoIP, nu player obisnuit.
Solutia: un MethodChannel nativ pe iOS care reactiveaza sesiunea VoIP si comuta explicit output-ul. Apoi reconfirmam ruta audio imediat dupa oprirea ringback-ului, fara sa asteptam starea connected.
In paralel, clientul a primit un strat WebRTC mai serios:
- colectare live
getStats()— RTT, bitrate, candidate path, fps, loss; - debug overlay pe ecranul apelului prin long-press;
- prewarm TURN/ICE cand deschizi un chat 1:1;
- limite pentru video bitrate si framerate;
- protectie impotriva conflictelor de renegotiation si ICE restart;
- cache ICE mai inteligent, ca un NAT prost sa nu ramana “copt” zece minute.
Telemetria pasiva este marele castig
Cel mai important: am incetat sa ghicim de ce s-a rupt apelul si am inceput sa strangem dovezi.
Clientul trimite la backend nu audio si nu continutul apelului, ci urme tehnice de calitate:
- samples aproximativ la fiecare 15 secunde: RTT, kbps in/out, relay/srflx path, fps, loss, jitter;
- summary la final: timpul de conectare, secunde fara audio, relay seconds, ICE restarts, motivul esecului;
- lifecycle events:
offer_sent,call_accepted,answer_received,ice_connected,connected,ice_restart_*, watchdog timeouts.
Backend-ul le salveaza in call_quality_samples, call_quality_summary si call_quality_events. Pentru investigatii exista endpoint-ul admin GET /v1/admin/calls/{call_id}/quality: ambele parti ale apelului, timeline, samples, events si un diagnosis preliminar.
Acum “nu ma aude” se transforma in fapte:
- caller are
audio_out = 0— cel mai probabil microfon sau sesiune audio pe acel dispozitiv; - caller are
audio_out > 0, iar callee areaudio_in = 0— inspectam reteaua, ICE sau TURN; - ambele parti au
audio_in > 0, dar plangerea ramane reala — probabil output route sau audio focus.
Diferenta este uriasa. Fara telemetrie, te certi cu senzatii. Cu telemetrie, te uiti la doua dispozitive si vezi unde a murit semnalul.
Urmatorul pas: admin dashboard cu filtre si grafice. JSON endpoint plus SQL ajung pentru moment, dar nu vreau sa traiesc acolo la nesfarsit.
Retea, offline si “Connecting…”
Alta durere din beta: deschizi aplicatia fara retea si ajungi la inregistrare, desi esti deja logat. Acum sesiunea se restaureaza din secure storage, apare “Connecting…”, iar chat-urile cache-uite raman disponibile. Acelasi indicator apare in header-ul chat-ului deschis cand socket-ul cade.
Nu este cea mai zgomotoasa functie, dar este una dintre cele mai umane. Utilizatorul nu trebuie sa inteleaga ca backend-ul nu raspunde, WebSocket se reconecteaza, iar token-urile traiesc separat de UI. El trebuie sa deschida aplicatia si sa vada: “sunt tot aici, datele nu au disparut, reteaua revine”.
CallKit a devenit si el mai calm: UI-ul sistem al apelului se inchide cand utilizatorul accepta in aplicatie. Altfel apar doua ringtone-uri si senzatia ca telefonul se cearta cu sine.
Push notification-urile au mers pe Variant A: notificarea contine un hint despre tipul mesajului, dar fara text si fara continut privat. Asa iOS si Android pot afisa ceva util fara sa transformam push-ul intr-o scurgere de date.
Media: mini-player peste tot shell-ul
Exista acum un mini-player jos, pe tot shell-ul: chat-uri, oameni, apeluri, setari. Are coperta, titlu marquee, play/pause, linie de progres si controale compacte. Functioneaza pentru muzica din playlist-uri si pentru mesajele vocale din chat.
Ca ticket, suna aproape decorativ. In folosirea zilnica, nu e. Cand un mesaj vocal continua sa cante in timp ce treci intre ecrane, iar muzica nu dispare la orice tap, aplicatia incepe sa se simta intreaga. Nu “deschis, trimis, inchis”, ci un loc in care poti sta cateva minute fara sa te lovesti de muchii.
Ce ramane deschis
E normal sa vorbim nu doar despre ce a iesit.
- Video circles inca se lovesc de limitele blob-ului si au nevoie de transcode normal pentru calitatea de cerc. Un clip lung la bitrate mare poate esua la trimitere.
- Clientul Mac este in dezvoltare activa. Mobile fan-out si recovery paths exista, dar desktop inca nu este in store.
- Call-quality dashboard nu exista inca. JSON exista; un UI util de investigatie nu.
- Retention si agregate de apeluri — daily failed %, p95 connect time si metrici similare — sunt in plan.
- Thumbnail-cache si media-index pentru galeria chat-ului raman urmatorul mare castig pentru memorie si viteza.
Luna de dupa beta din aprilie nu a fost despre perfectiune. A fost despre trecerea de la “pare ca merge pe telefonul meu” la un GChat cu dispozitive independente, profiluri reale, offline onest si apeluri care pot fi investigate.
Imi place etapa asta. E mai putin romantica, pentru ca bug-urile devin concrete si uneori neplacute. Dar aici produsul inceteaza sa fie demo si incepe sa se poarte ca ceva ce nu ti-e rusine sa folosesti in fiecare zi.