
बिना झिलमिलाहट के लाइव रिपोर्ट स्ट्रीम करने का तरीका
जब आपकी मीटिंग खत्म होती है, तो GeekBye की समरी अब आपको स्पिनर घूरने पर मजबूर करने के बजाय लाइव भरती जाती है। स्ट्रीमिंग UI को झटकेदार के बजाय शांत महसूस कराने के लिए हमें झिलमिलाहट दो बार सुलझानी पड़ी — एक बार स्ट्रक्चर्ड फ़ील्ड के लिए, एक बार markdown के लिए — और दूसरी बार का हल एक ऐसा रेंडरर था जो हमारे पास पहले से मौजूद था।
इस रिलीज़ से पहले, 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 ने हमें सिखाई तीन बातें
- स्ट्रीम होने वाले फ़ील्ड बफ़र करो, हर chunk पर एक बार पब्लिश करो। हर server-sent event को अपना खुद का state अपडेट चलाने देना ही झिलमिलाहट है। एक ref संचायक और हर chunk पर एक अकेला पब्लिश, एक कभी unmount न होने वाले ट्री में डालने से, बेचैन जुड़ाई शांत भराव में बदल जाती है।
- जो कुछ भी स्ट्रीम होता है उसे एक स्ट्रीमिंग-सजग रेंडरर चाहिए। जो रेंडरर हर टोकन पर पूरी स्ट्रिंग दोबारा पार्स करता है वह आधी-अधूरी markdown पर झिलमिलाएगा। अधूरे इनपुट के लिए बने रेंडरर का इस्तेमाल करो — और अगर तुम्हारे पास किसी दूसरी सतह के लिए पहले से एक है, तो दूसरा बनाने से पहले उसे पुनः उपयोग करो।
- रिच कॉपी clipboard फ़ॉर्मेट के बारे में है, सीरियलाइज़ेशन के बारे में नहीं। इमेज एम्बेड किया हुआ
text/htmlऔर एकtext/plainफ़ॉलबैक लिखो। clipboard दोनों ढोने के लिए ही बनाया गया था; उसका इस्तेमाल करो।
यह उस विश्वसनीयता-और-निखार की कहानी का पाँचवाँ अध्याय है जो GeekBye v2 बनती है। पिछले अध्याय के लिए, इयरबड्स वाला बग (v1.6.12) देखें; और जहाँ यही server-sent-events वाला विचार आगे चलकर एक पूरा फ़ॉलबैक ट्रांसपोर्ट बन गया, उसके लिए जब फ़ायरवॉल WebSockets रोक दे तब लाइव ट्रांसक्रिप्शन (v2.0.8) देखें; और पूरे चाप के लिए, सॉफ़्टवेयर को परिपूर्णता तक पहुँचाकर भेजने की शारीर-रचना देखें।