Săptămâna trecută a fost plină pentru GChat. Nu în sensul «am livrat o sută de commit-uri într-o noapte». Mai degrabă în sensul «azi apelurile sunt super, mâine aplicaţia moare când apelez, poimâine build-ul nu merge pentru că sincronizarea discului a duplicat foldere». Aşa arată beta-ul cu prieteni pe server şi TestFlight pe telefon.
Scriu ca să nu mai reconstruiesc durerea după feeling peste şase luni.
Apeluri şi WebRTC: fantoma era în plugin
Am iterat la starea sesiunii, la comportamentul sesiunii audio pe iOS, la UI pentru apeluri primite. Uneori totul se alinia. Uneori aplicaţia crapa în clipa în care porneai apelul. Logurile pe server arătau ok: apel creat, push trimis. Deci bug-ul era pe client.
Stack-ul din Xcode nu ne-a aruncat în Dart-ul nostru sau în FastAPI. Ne-a aruncat în flutter_webrtc: postEvent programează treabă pe main queue şi apelează sink(event). Dacă Flutter a demolat deja EventChannel-ul până rulează blocul, sink-ul nu mai există şi primeşti un EXC_BAD_ACCESS clasic. Anotaţia _Nonnull din plugin nu schimbă realitatea la runtime.
Fix-ul din upstream e un nil-check înainte şi înăuntrul dispatch-ului. Am aplicat aceeaşi idee local şi am reconstruit. După asta apelurile nu mai semănau cu ruleta: serverul care acceptă un apel nu ar trebui să însemne că moare clientul doar pentru că WebRTC a trimis un eveniment după ce canalul s-a închis.
Lecţie la dosar: când crash-ul stă pe __postEvent_block_invoke, citeşte dependenţa nativă înainte să îţi anulezi ultimele două commit-uri proprii.
UI chat: detalii mici pe care le simt utilizatorii
În paralel am livrat tweak-uri UX care în ticket sună ca «ajustează padding», dar schimbă cum se simte aplicaţia.
Bulele de text au trecut prin mai multe treceri: padding în plus, lăţime care nu urmărea conţinutul, reacţii care umflau plăcuţele vocale. Pentru note vocale am mutat reacţiile într-un overlay ca să nu se lungească capsula.
Swipe-to-reply pentru un timp mergea doar pe mesajele tale din cauza direcţiei gestului. Am aliniat cu stil Telegram: mereu stânga, indiferent de autor.
Rândurile de apel din istoric au culoare: pierdut roşu, primit verde, ieşit albastru. Detaliu mic, lizibilitate instant.
Panoul emoji: închis la tap în afară, tab implicit sensibil când Recents e gol, tranziţie mai lină înapoi la tastatură. Sună uşor până împaci focus-ul, inset-ul tastaturii şi animaţia pe iOS.
Voce şi cerc video: lock-to-record în stil Telegram, haptic la start, control dedicat de anulare în modul blocat, încălzire permisiuni la lansare ca să nu rămână butonul de înregistrare blocat după dialogul sistem.
Ataşamente: PDF-uri în viewer-ul sistem, MP3 inline cu acelaşi drum audio ca notele vocale.
Prezenţă şi zombie pe backend
Prietenii au raportat: aplicaţie închisă de ore, în header încă «online». Am strâns clientul cu timer în fundal plus logică de disconnect la lifecycle. Pe server am rezolvat un caz limită: dacă un worker WebSocket moare brutal, Redis putea păstra o intrare fantomă pentru conexiune. Atunci nici janitor-ul atent nu mai înţelege că utilizatorul a plecat.
Am pus TTL pe cheile de conexiune cu refresh cât sesiunea trăieşte, plus reconciliere între mulţimea online şi numărul real de socket-uri. După deploy, offline-ul nu mai «lipsea» o zi fără motiv.
iCloud şi build iOS: misiune laterală
O parte din arbore locuia sub iCloud Drive. OK pentru Markdown. Groaznic pentru Pods, DerivedData şi binare Flutter: atribute extinse, foldere duplicate gen nanopb 2 din conflicte de sync, şi dintr-o dată treizeci şi cinci de simboluri duplicate la linkare.
Am mutat clientul mobil sub ~/Developer/ şi am lăsat backend-ul şi landing-ul unde fluxul GitHub e natural. La semnare a trebuit să curăţăm gunoiul din artefacte (wrapper codesign plus ditto). Nu e inginerie elegantă. E taxa pentru sincronizarea dosarului de dev.
Git-ul a încercat o dată să moară din ref-uri duplicate gen refs/remotes/origin/HEAD 2. Ştergerea fişierelor străine a readus repo-ul la lucru. Dacă iCloud îţi oglindeşte home-ul, uită-te după fişiere fantomă «copy 2» în .git.
Ce încă doare (spus direct)
Cercuri video: serverul limitează blob-ul la cam doisprezece megabyte, iar clientul a trăit cu alt model mental şi fără pas dedicat de transcodare pentru calitate «cerc». Efectul net: un clip lung la bitrate mare loveşte plafonul şi nu pleacă, deşi bucăţi din pipeline par aproape verzi. Flip camera pentru cerc e legat la nivelul serviciului de înregistrare, dar un control vizibil încă cere lucru la gesturi (nu poţi onest să flipezi în timpul înregistrării cu API-ul de cameră pe care îl folosim acum). Asta e următoarea tranşă, nu ceva de prefăcut că e gata.
Săptămâna nu a fost despre «totul perfect». A fost despre oameni reali cu build-uri reale, bug-uri reale, şi despre mine putând spune: iată ce am reparat, iată ce am lăsat intenţionat pentru mai târziu. Dacă citeşti asta dintr-un viitor în care GChat e în magazin, săptămâni ca asta sunt motivul.