
หน้า Login นั่นแหละคือ Demo
GeekBye v1.7.5 เพิ่ม screenshot ของโปรดักต์ลงหน้า login แล้วลบทิ้งภายในวันเดียวกัน — ก่อนจะสร้างใหม่ด้วยคอมโพเนนต์ของจริงแทน แถมด้วย: ผู้ใช้ที่เร็วกว่า OCR และ style commit 25 ตัวในวันเดียวหน้าตาเป็นยังไงกันแน่
GeekBye v1.7.5 คือรีลีสหนึ่งสัปดาห์ที่มีบุคลิกแตกเป็นสองขั้ว: 37 คอมมิต โดย 28 ตัวเป็นการรีดีไซน์หน้า login — และ 25 ตัวจากในนั้นคือการจูนจิ๋ว ๆ แบบ style(login) ที่ลงจอดทั้งหมดในวันเดียว มันยังเป็นรีลีสที่แก้ race ที่ให้บทเรียนได้ดีที่สุดตัวหนึ่งในโค้ดเบสด้วย ทั้งสองเรื่องคุ้มค่าแก่การเล่า และมาบรรจบกันที่ความคิดเดียวกัน: หน้า login คือสิ่งแรกที่ผู้ใช้ที่ยังไม่ล็อกอินเห็น เพราะฉะนั้นมันควรเป็นตัวโปรดักต์เอง ไม่ใช่รูปถ่ายของโปรดักต์
screenshot ที่อยู่ได้ราวห้าชั่วโมง
การรีดีไซน์เริ่มต้นแบบขนบเดิม จัดโครงใหม่เป็นสองคอลัมน์ วางช่อง sign-in ไว้ซ้าย แล้วโชว์โปรดักต์ทางขวา — เป็นรูปภาพ คอมมิตช่วงสายทำแบบนั้นเป๊ะ: เวลา 11:57 screenshot พรีวิวของจริงเข้ามาอยู่ใน repo เป็น PNG ขนาด 114 KB และถูกอัปเดตหนึ่งครั้งในอีกหกนาทีถัดมา
พอถึง 17:04 มันก็หายไปแล้ว คอมมิตหักเลี้ยวแทนที่รูป static ด้วยคอมโพเนนต์ของจริง 344 บรรทัด — แผง LoginDemo ที่ประกอบจากชิ้นส่วนเล็ก ๆ แปดชิ้นซึ่งจำลอง overlay UI ตัวจริง: pill นำทางสำหรับอัดเสียงพร้อมแท่ง EQ เต้นตุบ ๆ และปุ่มหยุด, แผงแชทพร้อมแท็บ Chat/Transcript, คำตอบของ assistant ที่ render มาให้ดู (อธิบาย useEffect ของ React — ช่างเข้ากับสถานการณ์), ปุ่มลัด "Tell me more / Simplify / Show example", แถว live-prompt และช่องพิมพ์ Assist แบบ ⌘⏎
ขอเป๊ะกับคำว่า "demo" หน่อย เพราะมันเป็นคำที่เคลมเกินจริงได้ง่าย: ปุ่มมี hover state ของจริง — glow, shimmer, transition — แต่ไม่มีอะไรต่อสายเข้ากับการคลิกเลย และช่องพิมพ์เป็นแบบ read-only มันไม่ใช่ sandbox สิ่งที่มันเป็นคือคลังศัพท์ UI ตัวจริงของโปรดักต์ที่ถูก render สด ๆ: โลโก้ asset ตัวเดียวกัน, การตกแต่งขอบ gradient และ backdrop-blur แบบเดียวกัน, การ import productName ตัวเดียวกับที่ระบบ white-label ใช้ — demo จึง rebrand ตัวเองอัตโนมัติให้กับแต่ละโปรดักต์ที่ build จากโค้ดเบสนี้ screenshot ทำแบบนั้นไม่ได้สักอย่าง screenshot เก่าตั้งแต่วันที่คุณถ่ายมัน เบลอบนจอที่ไม่ตรงสเปก และถูกตีตราแบรนด์ถาวรเป็นโปรดักต์ใดก็ตามที่คุณบังเอิญแคปมา คอมโพเนนต์คมชัดทุก DPI ทันสมัยเสมอ และถูกต้องสำหรับทุกแบรนด์ — เพราะมันไม่ใช่รูปถ่ายของ design system มันคือ design system
ส่วน PNG ที่ถูกลบไปนั้น ยังคงอยู่ใน repo ที่ตำแหน่ง release tag — กลายเป็นไฟล์กำพร้า ไม่มีอะไรอ้างถึงมันอีก ทุกโค้ดเบสล้วนแบกซากของแนวทางที่พ่ายแพ้ติดตัวไว้สักสองสามชิ้นเสมอ
style commit 25 ตัวในวันเดียวหน้าตาเป็นยังไง
commit log ของวันนั้นวันเดียวคือสมุดสเก็ตช์ของดีไซเนอร์ ที่ถูกเก็บรักษาไว้อย่างซื่อตรงผิดปกติ อัตราส่วนคอลัมน์ไป 40/60 แล้วก็ 45/55 ป้าย "Works with" เหนือโลโก้แพลตฟอร์มถูกเพิ่มตอนเที่ยง ถูกย้ายตำแหน่ง แล้วถูกถอดออก — อยู่ได้ราวครึ่งวัน แถวโลโก้แพลตฟอร์มเอง (Google Meet, Teams, Zoom, Slack, Webex) ที่เพิ่มมาตอนเช้า 10:59 ถูกลบทิ้งทั้งแถวตอน 22:08 คืนนั้น ส่วน footer ต้องผ่านมหากาพย์สี่แนวทางกว่าจะจัดวางลงตัว: ครอบด้วย wrapper container ตัวเดียว แล้วถอดคลาส centering ออก แล้วจัดกึ่งกลางด้วย flexbox แล้วสุดท้ายก็แค่ขยับมันลงไปชิดขอบล่างอีกนิด
จะอ่านทั้งหมดนี้ว่าเป็นการวนอยู่กับที่ก็ง่ายอยู่ แต่เราอ่านมันว่าเป็นรูปร่างที่แท้จริงของงานดีไซน์: คุณลู่เข้าหาคำตอบด้วยการ render มองดู แล้วปรับ — และการทำแบบนั้นด้วยคอมมิตเล็ก ๆ 25 ตัวแทนที่จะ squash เป็น "redesign login page" ก้อนเดียว หมายความว่าตัวกระบวนการค้นหาเองถูกบันทึกไว้ พอมีคนถามว่า "เราเคยลองแถวโลโก้ครั้งหนึ่งไม่ใช่เหรอ" คำตอบก็อยู่ในประวัติ พร้อม timestamp
ผู้ใช้ที่เร็วกว่า OCR
เรื่องราวฝั่งวิศวกรรมของรีลีสนี้เกิดขึ้นก่อนหน้านั้นห้าวัน ในเชนการแก้สามคอมมิต ตั้งแต่ v1.5.12 การกด Cmd+H จะเข้าคิว screenshot พร้อมฟีดแบ็กทันที: thumbnail โผล่ในคิวทันใด ส่วนการสกัดข้อความ — Apple Vision OCR ในเครื่อง ใช้เวลาราวหนึ่งถึงสองวินาที — รันอยู่เบื้องหลัง อีเวนต์เข้าคิวถูกส่งออกไปพร้อม ocrText: '' ตรง ๆ แบบนั้นเลย พร้อมคอมเมนต์ที่สัญญาว่าเดี๋ยวจะมาเติมให้ทีหลัง
นั่นเป็น optimization ที่ดี และมันก็ผลิต race ออกมาหนึ่งตัว ผู้ใช้มือไวกด Cmd+H แล้วกดส่งคำถามทันที ฟิลด์ข้อความของ screenshot ยังว่างอยู่ request บินออกไปโดยไม่มีเนื้อหาบนจอเลยแม้แต่นิด และโมเดลก็ตอบ — ตามความจริง แบบชวนหัวเสียสุด ๆ — ว่า "I cannot see images" (ฉันมองไม่เห็นรูปภาพ) เส้นทางแบบซื่อ ๆ นี้ไม่ได้พังด้วย error; มันพังด้วยคำตอบที่ถูกต้องต่อคำถามที่ผิด ซึ่งเป็นวิธีพังของซอฟต์แวร์ที่ชวนงงที่สุด
ตัวแก้เอางาน async ที่ optimization เลื่อนไว้มากั้นเส้นทางการ submit: ถ้า screenshot ในคิวตัวไหนยังมี OCR text ว่างอยู่ request จะจอดรอ และ UI แสดงตัวบอกสถานะ "Reading screen..." (กำลังอ่านหน้าจอ); useEffect จะ fire ทันทีที่ข้อความมาถึง แล้วส่ง request พร้อมเนื้อหาที่สกัดครบถ้วน ใน diff มีดีเทลเฉพาะทาง React สองจุดที่ควรขโมยไปใช้: คิวของ screenshot ถูก mirror เข้าไปใน ref ที่ sync ระหว่าง render เพราะเชนของ closure ใน event handler จับ state ที่ค้างเก่าไว้; และ request ที่จอดรอถูกส่งตรง ๆ แทนที่จะผ่าน hook คิว request แบบ generic ด้วยเหตุผล stale closure เดียวกัน
และมีหักมุมแบบตรงไปตรงมาบันทึกอยู่ใน commit message เลย: tracing log ที่เพิ่มมาเพื่อล่า race ตัวนี้เผยให้เห็นบั๊กตัวที่สองบนฝั่ง backend — การประกอบ prompt ทำ screenshot text หล่นหายทั้งก้อนใน code path หนึ่ง อาการเดียว บั๊กสองตัว เจอได้ด้วยการติดเครื่องมือวัดทั้งเชนแล้วเฝ้าดูว่าข้อความหายไปตรงไหน เชนนี้จบลงด้วยการตัดสินใจเชิงโปรดักต์ เก้านาทีหลังการแก้ race: Cmd+H เข้าคิว screenshot สำหรับวิเคราะห์แบบ batch ส่วน Cmd+Enter จะแคปหน้าจอปัจจุบันสดใหม่เสมอ — สอง workflow ที่ชัดเจน แทน workflow กำกวมตัวเดียว (ส่วน Assist ของ Cmd+Enter ทำอะไรจริง ๆ นั้นเป็นโพสต์ของมันเอง: ความช่วยเหลือจาก AI แบบทันทีกับอะไรก็ตามที่คุณกำลังมองอยู่)
โคดาชิ้นเล็กจากสัปดาห์เดียวกัน สำหรับผู้อ่านบทว่าด้วย white-label: User-Agent แบบ Product/version (platform) ได้ตัวแก้สำหรับโหมด dev เพราะ app.getVersion() ใน Electron ที่ยังไม่ได้ package จะคืนเวอร์ชันของ Electron ไม่ใช่ของโปรดักต์ สตริงระบุตัวตนก็อย่างที่รู้กัน รั่วอยู่ในท่อประปาเสมอ
สามสิ่งที่รีลีสนี้สอนเรา
- จง render โปรดักต์ อย่าถ่ายรูปมัน demo บนหน้า login ที่สร้างจากคอมโพเนนต์จริง rebrand ตัวเองอัตโนมัติ รอดทุกความหนาแน่นของจอ และเก่าไม่เป็น แนวทาง screenshot ถูกลองแล้วถูกลบภายในวันทำงานเดียว — การทดลองราคาถูก คำตัดสินชัดเจน
- ทุก optimization แบบ async ต้องมีประตูกั้นตรงจุดที่ใช้งาน "ฟีดแบ็กทันทีพร้อม OCR text ว่าง" เป็นการตัดสินใจด้าน UX ที่ถูกต้อง แต่เส้นทางการ submit ต้องเรียนรู้ที่จะรอสิ่งที่ optimization เลื่อนไว้ ถ้าคุณเลื่อนงานออกไป จงหาผู้บริโภคผลลัพธ์ของมันให้ครบทุกราย แล้วสอนพวกเขาให้แยกระหว่าง "ว่างเปล่า" กับ "ยังไม่พร้อม"
- ติดเครื่องมือวัดทั้งเชน ไม่ใช่แค่ผู้ต้องสงสัย tracing log ตาม screenshot text ตั้งแต่ต้นจนจบ — จาก shortcut handler ไป IPC ไป API client — และนั่นคือเหตุผลที่การสืบสวนครั้งเดียวปิดบั๊กได้สองตัว โดยตัวหนึ่งอยู่อีกฟากของเครือข่าย
สำหรับบทก่อนหน้าของเรื่องราวยุค v1 ดูการประชุมที่กลับมาหาเจอได้จริง ๆ (v1.7.3); และสำหรับภาพรวมทั้งเส้นเรื่อง ดูกายวิภาคของการส่งมอบซอฟต์แวร์ให้สมบูรณ์แบบ