Steven
Steven6 मिनट पढ़ें

बिना झिलमिलाहट के लाइव रिपोर्ट स्ट्रीम करने का तरीका

जब आपकी मीटिंग खत्म होती है, तो GeekBye की समरी अब आपको स्पिनर घूरने पर मजबूर करने के बजाय लाइव भरती जाती है। स्ट्रीमिंग UI को झटकेदार के बजाय शांत महसूस कराने के लिए हमें झिलमिलाहट दो बार सुलझानी पड़ी — एक बार स्ट्रक्चर्ड फ़ील्ड के लिए, एक बार markdown के लिए — और दूसरी बार का हल एक ऐसा रेंडरर था जो हमारे पास पहले से मौजूद था।

इंजीनियरिंग
फ्रंटएंड
स्ट्रीमिंग
GeekBye रिलीज़
बिना झिलमिलाहट के लाइव रिपोर्ट स्ट्रीम करने का तरीका

इस रिलीज़ से पहले, GeekBye मीटिंग खत्म करने का मतलब था एक स्पिनर घूरना। ऐप आपका पूरा ट्रांसक्रिप्ट विश्लेषण के लिए भेजता, पूरी समरी का इंतज़ार करता — स्कोर, मुख्य बिंदु, एक्शन आइटम, सब कुछ — और फिर एकसाथ स्क्रीन पर पटक देता। काम तो करता था, पर महसूस धीमा होता था, क्योंकि जब तक वह काम करता, आप एक खालीपन घूरते रहते।

GeekBye v1.6.13–v1.6.15 ने उसे एक ऐसी रिपोर्ट में बदल दिया जो बनते ही, फ़ील्ड-दर-फ़ील्ड, लाइव बहती हुई आती है। दिलचस्प हिस्सा स्ट्रीमिंग खुद नहीं है — बल्कि वह सब है जो हमें करना पड़ा ताकि स्ट्रीमिंग झटकेदार न दिखे। शांत स्ट्रीमिंग और लड़खड़ाती स्ट्रीमिंग एक ही फ़ीचर हैं, बस अमल में ज़मीन-आसमान का फ़र्क है।

झिलमिलाहट, भाग एक: हर chunk को React झिंझोड़ने मत दो

समरी एक ठोस गुच्छा नहीं है — यह स्ट्रक्चर्ड फ़ील्ड (एक कुल स्कोर, मुख्य बिंदु, एक्शन आइटम, वगैरह) हैं जिन्हें बैकएंड server-sent events पर एक-एक करके भेजता है। इसे रेंडर करने का भोला तरीका है हर इवेंट आने पर React state अपडेट करना।

ऐसा करो, और झिलमिलाहट मिलेगी। हर फ़ील्ड का आना अपना खुद का re-render छेड़ता है; जो कंपोनेंट "खाली" दिखा रहे थे वे mount होते हैं, फिर unmount, फिर "लोडेड" के रूप में remount; हर पैकेट पर लेआउट झटका खाता है। रिपोर्ट सबसे बुरे संभव तरीके से आपकी आँखों के सामने खुद को जोड़ती है — साफ़ दिखते हुए, बेचैनी से।

हल एक दो-हिस्सों वाला अनुशासन है। पहला, आने वाले फ़ील्ड को एक ref — एक बदल सकने वाला धारक जो रेंडर नहीं छेड़ता — में जमा करो, और हर chunk पर एक ताज़ा कॉपी के साथ React state में एक बार पब्लिश करो, ताकि ट्री हर सूक्ष्म-इवेंट पर नहीं बल्कि सोच-समझकर अपडेट हो। दूसरा, चाहे स्ट्रीमिंग चल रही हो या पूरी हो चुकी हो, हमेशा एक ही स्थिर कंपोनेंट ट्री रेंडर करो, जिन फ़ील्ड अभी नहीं आए उनके लिए इनलाइन प्लेसहोल्डर रखते हुए:

// accumulate in a ref, publish once per chunk
partialRef.current = { ...partialRef.current, [field]: value }
setPartial({ ...partialRef.current })

// one tree, never swapped; missing fields show a placeholder in place
const report = isStreaming ? partial : saved
<Score>{report.overallScore ?? '…'}</Score>

स्कोर दिखाने वाला कंपोनेंट कभी unmount नहीं होता — जब तक संख्या नहीं आती वह बस दिखाता है, फिर संख्या। कुछ भी झटका नहीं खाता। और पूरी तरह जुड़ी हुई ऑब्जेक्ट अंत में भी लोकल डेटाबेस में कैश हो जाती है, इसलिए स्ट्रीमिंग UI और टिकाऊ भंडारण आपस में लड़े बिना साथ रहते हैं।

झिलमिलाहट, भाग दो: वह रेंडरर जो हमारे पास पहले से था

दूसरी झिलमिलाहट ज़्यादा सूक्ष्म है और खुद markdown के भीतर बसती है। रिपोर्ट समृद्ध markdown रेंडर करती है — शीर्षक, बोल्ड, सूचियाँ, कोड ब्लॉक। पर स्ट्रीम के बीचोबीच आने वाली markdown, किसी भी क्षण, आधी-अधूरी होती है। एक सूची जिसमें अब तक बस एक बुलेट है। एक कोड फ़ेंस जो खुला तो है पर बंद नहीं हुआ। एक बोल्ड मार्कर जिसका अभी कोई जोड़ीदार नहीं।

एक परंपरागत markdown रेंडरर हर टोकन पर पूरी स्ट्रिंग दोबारा पार्स करता है, और वह आधी-अधूरी अवस्था हर बार किसी अलग चीज़ में पार्स होती है — इसलिए सूची झिलमिलाती है, कोड ब्लॉक खुलता-बंद होता चमकता है, टोकन पूरे होते-होते लेआउट उछलता है। यह वही बेचैन जुड़ाई है, बस एक परत नीचे।

हल लगभग शर्मनाक हद तक पास ही था: हम लाइव AI चैट के लिए एक स्ट्रीमिंग-सजग markdown रेंडरर पहले से ही इस्तेमाल कर रहे थे। एक रेंडरर जो अधूरे टोकन झेलने के लिए बना है — आधी-अधूरी markdown को स्थिरता से रेंडर करता है और उसे तभी टिकने देता है जब टोकन पूरे हों — बजाय हर बार शुरू से दोबारा पार्स करने के। रिपोर्ट को बस वही इस्तेमाल करना था। यही बिल्कुल यही समस्या हमने महीनों पहले चैट के लिए सुलझा ली थी; रिपोर्ट का "हल" था यह पहचानना कि औज़ार हमारे पास पहले से है और उसे एक दूसरी सतह की ओर मोड़ देना। पुनः उपयोग ने फिर से बनाने को हरा दिया।

बोनस: रिच कॉपी clipboard फ़ॉर्मेट की समस्या है

एक बार रिपोर्ट अच्छी दिखने लगी, तो लोग उसे पेस्ट करना चाहते थे — किसी डॉक्युमेंट में, किसी ईमेल में — और फ़ॉर्मेटिंग तथा स्क्रीनशॉट बरकरार रखते हुए। सहज प्रवृत्ति है रिपोर्ट को एक markdown स्ट्रिंग में सीरियलाइज़ करना और उम्मीद करना कि गंतव्य उसे रेंडर कर देगा। आमतौर पर वह नहीं करता।

असली जवाब यह है कि clipboard एक साथ कई प्रस्तुतियाँ रखता है। तो हम दो लिखते हैं: एक HTML वर्शन जिसमें फ़ॉर्मेटिंग बरकरार है और स्क्रीनशॉट इनलाइन इमेज के रूप में एम्बेड हैं, और उन गंतव्यों के लिए एक प्लेन-टेक्स्ट फ़ॉलबैक जो HTML नहीं लेते। किसी रिच एडिटर में पेस्ट करो और आपको तस्वीरों सहित फ़ॉर्मेट की हुई रिपोर्ट मिलती है; किसी प्लेन टेक्स्ट बॉक्स में पेस्ट करो और आपको साफ़-सुथरा टेक्स्ट मिलता है। "रिच कॉपी" कभी सीरियलाइज़ेशन की समस्या थी ही नहीं — यह सही clipboard फ़ॉर्मेट चुनने की समस्या थी।

उसी रिलीज़ ने रिपोर्ट को चैट-शैली के Me / Them लेबल भी दिए, जिसमें दोनों वक्ता आमने-सामने की तरफ़ संरेखित हैं, ताकि एक ट्रांसक्रिप्ट उसी बातचीत की तरह पढ़ा जाए जो वह असल में थी, और आपके अपने स्क्रीनशॉट को इससे मेल खाने के लिए उनकी अपनी तरफ़ हटा दिया। (इस दौर की एक रिलीज़, v1.6.14, एक शुद्ध रीबिल्ड थी — वहाँ कोई कहानी नहीं है, और यह कह देना ईमानदारी है।)

स्ट्रीमिंग UI ने हमें सिखाई तीन बातें

  1. स्ट्रीम होने वाले फ़ील्ड बफ़र करो, हर chunk पर एक बार पब्लिश करो। हर server-sent event को अपना खुद का state अपडेट चलाने देना ही झिलमिलाहट है। एक ref संचायक और हर chunk पर एक अकेला पब्लिश, एक कभी unmount न होने वाले ट्री में डालने से, बेचैन जुड़ाई शांत भराव में बदल जाती है।
  2. जो कुछ भी स्ट्रीम होता है उसे एक स्ट्रीमिंग-सजग रेंडरर चाहिए। जो रेंडरर हर टोकन पर पूरी स्ट्रिंग दोबारा पार्स करता है वह आधी-अधूरी markdown पर झिलमिलाएगा। अधूरे इनपुट के लिए बने रेंडरर का इस्तेमाल करो — और अगर तुम्हारे पास किसी दूसरी सतह के लिए पहले से एक है, तो दूसरा बनाने से पहले उसे पुनः उपयोग करो।
  3. रिच कॉपी clipboard फ़ॉर्मेट के बारे में है, सीरियलाइज़ेशन के बारे में नहीं। इमेज एम्बेड किया हुआ text/html और एक text/plain फ़ॉलबैक लिखो। clipboard दोनों ढोने के लिए ही बनाया गया था; उसका इस्तेमाल करो।

यह उस विश्वसनीयता-और-निखार की कहानी का पाँचवाँ अध्याय है जो GeekBye v2 बनती है। पिछले अध्याय के लिए, इयरबड्स वाला बग (v1.6.12) देखें; और जहाँ यही server-sent-events वाला विचार आगे चलकर एक पूरा फ़ॉलबैक ट्रांसपोर्ट बन गया, उसके लिए जब फ़ायरवॉल WebSockets रोक दे तब लाइव ट्रांसक्रिप्शन (v2.0.8) देखें; और पूरे चाप के लिए, सॉफ़्टवेयर को परिपूर्णता तक पहुँचाकर भेजने की शारीर-रचना देखें।

संबंधित लेख

एक कनेक्शन टूटने से पूरा ऐप क्रैश नहीं होना चाहिए — पर हमारा हुआ
Steven
Steven7 मिनट पढ़ें

एक कनेक्शन टूटने से पूरा ऐप क्रैश नहीं होना चाहिए — पर हमारा हुआ

जब हमारा बैकएंड मीटिंग के बीच में ऑफ़लाइन हुआ, तो उसने सिर्फ़ ट्रांसक्रिप्शन नहीं रोका — उसने पूरा ऐप क्रैश कर दिया। वजह थी एक अकेली अनहैंडल्ड इवेंट, और फ़िक्स दस लाइनों का था। यह उन रिलीज़ों का गुच्छा है जिसने GeekBye को उन हालात से गुज़रते हुए भी साइन-इन बनाए रखा और कनेक्टेड बनाए रखा, जो पहले उसे मार डालते थे।

इंजीनियरिंग
विश्वसनीयता
Electron
सॉफ़्टवेयर को परफेक्शन तक शिप करने की एनाटॉमी: कोड रिव्यू ने वह पकड़ा जो टेस्ट न पकड़ सके
Steven
Steven7 मिनट पढ़ें

सॉफ़्टवेयर को परफेक्शन तक शिप करने की एनाटॉमी: कोड रिव्यू ने वह पकड़ा जो टेस्ट न पकड़ सके

GeekBye v2 सीरीज़ में बार-बार वही एक चीज़ होती रही: एक फिक्स डेवलपर की मशीन पर हर टेस्ट पास कर जाता है, और फिर कोड रिव्यू साबित कर देता है कि यह करीब-करीब सबके लिए फेल हो जाता। यह नौ रिलीज़ के पीछे की वर्कफ़्लो है — रिव्यू गेट, फिक्स-फर्स्ट कैच, और शिप-से-पहले-टेस्ट का अनुशासन जो 'मेरे यहाँ तो चलता है' को 'यह चलता है' में बदल देता है।

इंजीनियरिंग
प्रक्रिया
कोड रिव्यू
आपका Mac ऐप माइक्रोफ़ोन एक्सेस देता है — फिर हर बार खुलने पर उसे भूल जाता है
Steven
Steven7 मिनट पढ़ें

आपका Mac ऐप माइक्रोफ़ोन एक्सेस देता है — फिर हर बार खुलने पर उसे भूल जाता है

GeekBye ने माइक्रोफ़ोन की अनुमति माँगी, आपने दे दी, और यह चल गया। अगली बार खोलने पर: गायब। और ऐप System Settings → Microphone में कभी दिखा ही नहीं। गुनहगार एक macOS सुरक्षा फ़ीचर था जो चुपचाप ऐप को एक ऐसे पाथ से चला रहा था जो गायब हो जाता है — यहाँ पूरी जाँच और वह एक-प्रॉम्प्ट वाला फ़िक्स है।

macOS
अनुमतियाँ
इंजीनियरिंग