Real-time collaboration looks effortless until you pull back the curtain. One person types. Another deletes a line three paragraphs up. A third pastes a snippet from Stack Overflow. Somehow the document settles into a single, coherent state. Building that fluidity from scratch, with no prior experience in WebSockets or distributed state, sounds reckless. It also sounds like the right way to actually learn.
This project starts from zero. No borrowed boilerplate. No polished YouTube walkthroughs where the hard parts are skipped in a thirty-second montage. The goal is a collaborative code editor where multiple users can edit the same file simultaneously, seeing each other’s changes—and each other’s cursors—as they happen. Getting there will require figuring out transport layers, consistency models, and the thorny problem of merging concurrent edits without corrupting the document.
What “Real-Time” Actually Means
Most web applications are comfortable with request-response cycles. You submit a form, the server saves, you refresh the page. Real-time collaboration breaks that contract entirely. Every keystroke is an event that must propagate to every other connected client, usually in milliseconds, and arrive in an order that preserves meaning.
WebSockets are the obvious transport choice here because they maintain a persistent, full-duplex connection between client and server. Unlike HTTP polling, which wastes bandwidth asking “anything new?” every few seconds, a WebSocket stays open. When user A types a semicolon, that character becomes a message that travels through the socket to a central server, then fans out to users B and C. That part is relatively straightforward.
The hard part is what happens when B and C type at the exact same moment. If both changes hit the server nearly simultaneously, which one wins? If you simply broadcast messages in arrival order, you risk dropped characters or jumbled text. Naive last-write-wins strategies fail because they ignore intent. If I type “hello” at the start of line one while you type “world” at the start of line one, the result should not be a collision where one of us is erased. It should be “helloworld” or “worldhello,” deterministically chosen. Achieving that requires a synchronization strategy that understands the structure of the document.
Why Starting From Ground Zero Matters
There are excellent frameworks that hide this complexity. Yjs, Automerge, and Socket.IO can abstract away the pain and produce a working prototype in an afternoon. But using them without understanding the primitives underneath is like flying a plane on autopilot without knowing how to read the instruments. When turbulence hits—and in distributed systems, it always hits—you need to know whether the issue is in your network layer, your conflict resolution, or your data model.
The commitment here is to learn the concepts before relying on the libraries. That means manually reasoning through what happens when:
- A client disconnects mid-keystroke and reconnects ten seconds later
- Two users insert text at the same cursor position concurrently
- One user deletes a block that another user is actively editing
- The server crashes and a new node has to reconstruct the document state from scratch
Operational Transformation (OT) and Conflict-free Replicated Data Types (CRDTs) are the two dominant families of solutions for these problems. Google Docs famously built its early architecture on OT, which requires a central server to transform operations against each other before applying them. CRDTs, by contrast, are designed so that concurrent updates can be merged locally without coordination, making them attractive for peer-to-peer or edge-based setups. Choosing between them—or hybrid approaches—requires understanding their trade-offs in memory use, convergence guarantees, and implementation complexity. Reading about those trade-offs is not enough; the plan is to implement both naive and refined versions to see where they break.
The Rebuilds, Mistakes, and Dead Ends
הציפיות מכוונות בכנות. יהיו תקופות שבהן שום דבר לא יעבוד. ניסיון ראשון עשוי להשתמש ב-JSON patches פשוטים כדי לייצג שינויי טקסט, רק כדי לגלות של-JSON אין מושג של "אינדקס 5 בפסקה", כך ששתי הכנסות בו-זמניות באותו אינדקס ידרסו זו את זו במקום להתמזג. ניסיון שני עשוי לבנות יומן היסטוריה ליניארי מותאם אישית, רק כדי להבין ששידור מחדש של היומן הזה הוא סיוט של Big O כשהמסמך גדל. ניסיון שלישי עשוי להצליח להפעיל WebSockets באופן מקומי, ואז לקרוס ברשת אמיתית שבה אובדן חבילות (packet loss) ושיהוי (latency) משתנה כותבים מחדש את החוקים.
החיכוך הזה הוא המטרה. העתקה של מאגר קוד (repository) שעובד הייתה חוסכת את החקירה של הסיבה לכך שהתור (queue) מתרוקן בסדר הספציפי הזה, או מדוע השרת שומר version vector. בנייה מחדש של אותו רכיב שלוש פעמים היא איטית, אך היא מאלצת הבנה של הגבול שבין מה שה-framework עושה לבין מה שהלוגיקה שלך חייבת לטפל בו.
התיעוד של התהליך הזה לא יהיה סרטון של רגעים נבחרים. הוא יכלול גם את הצעדים השגויים. לדוגמה, בניית מודעות לנוכחות (presence awareness) — לדעת מי מחובר ואיפה נמצא הסמן שלו — נראית כמו פיצ'ר קוסמטי עד שמבינים שהיא תלויה באותו מודל עקביות (consistency model) של הטקסט עצמו. אם משתמש א' רואה את הסמן של משתמש ב' בעמודה 10, ואז משתמש ב' מכניס ארבעה תווים, לאן הסמן הזה זז? ללא הבנה משותפת של טופולוגיית המסמך, נתוני הנוכחות סוטים מהמציאות. פתרון לכך דורש קישור של מיקום הסמן לזהות של מבנה הנתונים שבבסיס, ולא רק לאינדקס המספרי שלו. אלו סוגי הפרטים שסרטי הדרכה מדלגים עליהם כי הם מייגעים, לא בגלל שהם לא חשובים.
מה הלאה
מפת הדרכים המיידית היא דלילה בכוונה. אבני הדרך הראשונות יהיו:
- שרת WebSocket גולמי המהדהד אירועי תווים, כדי להרגיש ממקור ראשון את ה-latency ואת מחזור חיי החיבור
- string buffer פשוט בצד הלקוח כדי להבין מדוע סדר הכנסה נאיבי נכשל תחת מצבי ריבוי (concurrency)
- CRDT שנבנה מאפס עבור רצפים מסודרים, ולו בצורה לא יעילה, כדי לראות את התכונה הקומוטטיבית בפעולה
- אינטגרציה הדרגתית עם ממשק עורך קוד אמיתי, ככל הנראה משהו כמו CodeMirror או Monaco, כדי להתמודד עם חוסר ההתאמה בין ה-API האימפרטיבי של העורך לבין הטבע הפונקציונלי של היסטוריית הפעולות
כל שלב ילווה ברציונל כתוב. למה הגישה הזו ולא אחרת? אילו הנחות הופרכו? איזו הפשטה (abstraction) דלפה?
תובנה אמיתית
התחלת פרויקט כזה ללא ניסיון ב-WebSockets או ב-CRDTs היא מרתיעה, אך מומחיות היא לעיתים קרובות רק בלבול חוזר עם תוויות טובות יותר. המטרה היא לא סיום מהיר. המטרה היא מערכת שהתנהגותה צפויה מכיוון שכל שכבה נבנתה בכוונה תחילה ולא יובאה מתוך תקווה.
אם בניתם תוכנה שיתופית בעבר — בין אם עורך טקסט, כלי עיצוב או מנוע סנכרון מצב משחק — שתפו את מצבי הכשל שתפסו אתכם לא מוכנים. אם גם אתם לומדים את המערכות הללו, עקבו אחרינו. הקוד יגיע לאט, והוא ייכתב מחדש לעיתים קרובות. יום 0 מתחיל עכשיו.
