1import{j as e}from"./main-CsRtcZnr.js";import{u as o}from"./index-CsJ-uLA8.js";const h={title:"Local-First in Production â FOSDEM 2026 Recap",description:"A short recap of my FOSDEM talk on building Planeâs collaborative Wiki with Yjs, the production failures that taught us the most, and the slides for anyone who couldnât make it.",date:"2026-02-02",readingTime:"6 min",image:"https://1txs4adwec.ucarecd.net/1bdcc90e-fe57-4387-ac20-bf3e2bc2b97e/f2d91c550ab4472198eba076fd1be4bb1.webp"};function i(s){const n={a:"a",code:"code",em:"em",figure:"figure",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",span:"span",strong:"strong",ul:"ul",...o(),...s.components},{EventCard:r,MasonryGallery:a,VideoPlayer:l}=n;return r||t("EventCard"),a||t("MasonryGallery"),l||t("VideoPlayer"),e.jsxs(e.Fragment,{children:[e.jsx(n.p,{children:"FOSDEM 2026 is done. Iâm still processing it."}),` 2`,e.jsx(n.p,{children:"A few months ago this was just a nervy announcement post. Now itâs a real memory: the room, the questions, the energy, and the very surreal moment of talking about Yjs and local-first systems in front of people Iâve been learning from."}),` 3`,e.jsx(n.p,{children:"This was my first international conference talk, and it meant a lot."}),` 4`,e.jsx(n.p,{children:"There was a quick AV reset at the start that ate about 10â13 minutes, so I ended up compressing the talk on the fly. The organizers handled it smoothly and kept everything moving, and I really appreciated the calm energy in the room."}),` 5`,e.jsx(n.h2,{children:"The Talk (Now Done)"}),` 6`,e.jsx(r,{title:"Local-First in Production: How We Built Plane's Collaborative Wiki with Yjs",date:"February 1, 2026",time:"10:00 - 10:30 CET",room:"K.3.201",location:"ULB Campus, Brussels",track:"Local-First, sync engines, CRDTs",url:"https://fosdem.org/2026/schedule/event/TCH8XQ-plane-wiki-local-first-in-production-with-yjs/"}),` 7`,e.jsx(n.h3,{children:"Watch the Recording"}),` 8`,e.jsx(l,{src:"https://www.youtube.com/watch?v=Tg3nMDTF0C4",title:"Local-First in Production: How We Built Plane's Collaborative Wiki with Yjs"}),` 9`,e.jsxs(n.p,{children:["If you're new here: I work on ",e.jsx(n.a,{href:"https://plane.so",children:"Plane"}),", an open-source project management tool (projects, wiki, AI, everything in one workspace). Our Wiki is collaborative, and Yjs is the engine that makes it real-time and local-first."]}),` 10`,e.jsx(n.p,{children:"This talk was a production story, not a research talk â how we built Plane's collaborative Wiki on Yjs, the architecture choices we made, and the sharp edges we hit while shipping it for real users."}),` 11`,e.jsx(n.h2,{children:"The Story I Opened With"}),` 12`,e.jsx(n.p,{children:"Last year we upgraded a dependency. Memory usage exploded. Seven pods went down. Kubernetes kept restarting them. WebSockets were basically unusable."}),` 13`,e.jsx(n.p,{children:"Our users? Zero data lost. They just kept editing."}),` 14`,e.jsxs(n.p,{children:["That moment framed the whole talk: ",e.jsx(n.strong,{children:"how did we get to a place where a catastrophic server incident didn't destroy user work?"})," The answer wasn't magic. It was careful design, hard lessons, and a lot of Murphy's Law along the way."]}),` 15`,e.jsx(n.h3,{children:"Moments from Brussels"}),` 16`,e.jsx(a,{images:[{src:"https://1txs4adwec.ucarecd.net/c181bc39-4c72-4c0f-9167-a372506d0d64/fodembrandon.webp",alt:"FOSDEM 2026 with Brandon"},{src:"https://1txs4adwec.ucarecd.net/31f2b5c6-7cf6-4d2c-8705-ca3cebb20582/brandonfosdem.webp",alt:"Brandon at FOSDEM"},{src:"https://1txs4adwec.ucarecd.net/61a352be-b66a-4799-9ca6-d379c2a9a096/HAUJ5jiXoAA3zbN.jpeg",alt:"FOSDEM moment"},{src:"https://1txs4adwec.ucarecd.net/27b26396-1bb6-474f-8e08-9ef7203ba03f/Screenshot20260204at54812PM1.webp",alt:"Plane at FOSDEM â center stage"},{src:"https://1txs4adwec.ucarecd.net/4affccb4-bc88-445d-b68f-a02a89ff1cca/HAESsMMWcAA3Tan.jpeg",alt:"FOSDEM experience"},{src:"https://1txs4adwec.ucarecd.net/4a85e78f-b903-4aec-b746-8a515a7193ee/HAEP5eEWEAAWIIy.jpeg",alt:"FOSDEM memory"},{src:"https://1txs4adwec.ucarecd.net/c49254f9-411c-4b3c-a130-a3c09101b1a2/HAAL5yIXcAEhC2f.jpeg",alt:"FOSDEM 2026"}],columns:3,gap:"md",className:"grayscale"}),` 17`,e.jsx(n.h2,{children:"What I Shared (The Production Bits)"}),` 18`,e.jsx(n.h3,{children:"1) The One Rule: Save the Binary"}),` 19`,e.jsxs(n.p,{children:["When working with Yjs, the ",e.jsx(n.strong,{children:"Y.Doc binary is the source of truth"}),". JSON/HTML are just views."]}),` 20`,e.jsx(n.p,{children:"We initially stored JSON editor content. When we added collaborative editing, we migrated to Yjs on demand. The first time a doc was opened, we converted it and loaded it into Yjs."}),` 21`,e.jsx(n.p,{children:"Hereâs the trap: if a user opens the doc and reads without editing, Hocuspocus doesnât persist (it only saves on change). That means the converted binary never gets s
21aved. The client keeps a copy in IndexedDB, but the server still has the old JSON."}),` 22`,e.jsx(n.p,{children:"Next time they open the doc, the server converts again â new client IDs â Yjs merges two versions of the same content â duplication and chaos."}),` 23`,e.jsxs(n.p,{children:["The fix was simple: ",e.jsx(n.strong,{children:"persist the binary immediately after conversion. Donât wait for edits."})]}),` 24`,e.jsx(n.figure,{"data-rehype-pretty-code-figure":"",children:e.jsx(n.pre,{tabIndex:"0","data-language":"ts","data-theme":"github-dark-dimmed",children:e.jsxs(n.code,{"data-language":"ts","data-theme":"github-dark-dimmed",style:{display:"grid"},children:[e.jsx(n.span,{"data-line":"",children:e.jsx(n.span,{style:{color:"#768390"},children:"// Snapshot the full Y.Doc as binary and persist it immediately"})}),` 25`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#F47067"},children:"const"}),e.jsx(n.span,{style:{color:"#6CB6FF"},children:" base"}),e.jsx(n.span,{style:{color:"#F47067"},children:" ="}),e.jsx(n.span,{style:{color:"#F47067"},children:" new"}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:" Doc"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"();"})]}),` 26`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#DCBDFB"},children:"applyUpdate"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(base, "}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:"encodeStateAsUpdate"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(ydoc));"})]})]})})}),` 27`,e.jsx(n.h3,{children:"2) Never Overwrite, Always Merge"}),` 28`,e.jsx(n.p,{children:"CRDTs converge by merging operations, not by overwriting blobs. If your storage layer replaces documents wholesale, youâve built Dropbox, not Google Docs."}),` 29`,e.jsx(n.p,{children:"This rule applied twice for us:"}),` 30`,e.jsxs(n.ul,{children:[` 31`,e.jsx(n.li,{children:"At the Yjs layer (operations merge)"}),` 32`,e.jsx(n.li,{children:"At the storage layer (we never overwrite, we always merge)"}),` 33`]}),` 34`,e.jsx(n.h3,{children:"3) The Fallback That Saved Us"}),` 35`,e.jsx(n.p,{children:"When WebSockets die, HTTP takes over."}),` 36`,e.jsx(n.p,{children:"Pseudocode for the fallback:"}),` 37`,e.jsxs(n.ul,{children:[` 38`,e.jsx(n.li,{children:"Fetch latest from DB"}),` 39`,e.jsx(n.li,{children:"Merge into local doc"}),` 40`,e.jsx(n.li,{children:"Save back"}),` 41`,e.jsx(n.li,{children:"Repeat every few seconds, with a manual save button too"}),` 42`]}),` 43`,e.jsx(n.p,{children:"Because CRDT merges are idempotent, it converges. Even when two clients race to save, the next cycle pulls both changes back together."}),` 44`,e.jsx(n.p,{children:"This fallback used to be our primary sync path in early days. It held up under pressure."}),` 45`,e.jsx(n.h3,{children:"4) What We Monitor Now"}),` 46`,e.jsx(n.p,{children:"The incident taught us what actually matters in production:"}),` 47`,e.jsxs(n.ul,{children:[` 48`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Docs per pod"})," (know when to scale)"]}),` 49`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Memory per doc"})," (binary size as proxy for âmonsterâ docs)"]}),` 50`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Update rate"})," (whoâs hammering the server)"]}),` 51`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Persist latency/failures"})," (early warnings)"]}),` 52`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Force-close codes"})," (4001/4002 for overload)"]}),` 53`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Time-to-first-content"})," (is caching really working?)"]}),` 54`]}),` 55`,e.jsx(n.p,{children:"We learned this the hard way. You donât have to."}),` 56`,e.jsx(n.h2,{children:"The Debugging Story (Murphyâs Law)"}),` 57`,e.jsx(n.p,{children:"We upgraded to Hocuspocus v3, did all the homework, rolled out on a Friday, and I even posted a confident tweet about it."}),` 58`,e.jsx(n.p,{children:"Then a huge customer onboarded that same day with massive tables. Memory climbed, pods restarted, WebSockets dropped, and memory never went back down."}),` 59`,e.jsxs(n.p,{children:["The bug was subtle: ",e.jsx(n.span,{"data-rehype-pretty-code-figure":"",children:e.jsx(n.co
59de,{"data-language":"plaintext","data-theme":"github-dark-dimmed",children:e.jsx(n.span,{"data-line":"",children:e.jsx(n.span,{children:"process.nextTick"})})})})," runs before Promise continuations."]}),` 60`,e.jsxs(n.p,{children:["The unload check ran ",e.jsx(n.em,{children:"before"})," a debounce flag cleared, so it thought the doc was still executing and never unloaded it. Docs stayed in memory forever."]}),` 61`,e.jsxs(n.p,{children:["The fix was a small change in the debounce utility: an ",e.jsx(n.span,{"data-rehype-pretty-code-figure":"",children:e.jsx(n.code,{"data-language":"plaintext","data-theme":"github-dark-dimmed",children:e.jsx(n.span,{"data-line":"",children:e.jsx(n.span,{children:"afterExecution"})})})})," callback that runs after the flag is cleared. Deterministic. No event loop roulette."]}),` 62`,e.jsx(n.h2,{children:"When Docs Get Too Big"}),` 63`,e.jsx(n.p,{children:"We also hit another sharp edge: huge pastes (like 50MB images). The gateway rejects with 413. One giant paste blocks all other saves. Deadlock."}),` 64`,e.jsx(n.p,{children:"Our solution: detect oversized docs and force-close the WebSocket with app-specific close codes (4001 for âdoc too bigâ, 4002 for âmemory pressureâ). We broadcast the force-close through Redis so every server drops the doc and frees memory."}),` 65`,e.jsx(n.figure,{"data-rehype-pretty-code-figure":"",children:e.jsx(n.pre,{tabIndex:"0","data-language":"ts","data-theme":"github-dark-dimmed",children:e.jsxs(n.code,{"data-language":"ts","data-theme":"github-dark-dimmed",style:{display:"grid"},children:[e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#F47067"},children:"export"}),e.jsx(n.span,{style:{color:"#F47067"},children:" enum"}),e.jsx(n.span,{style:{color:"#F69D50"},children:" CloseCode"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:" {"})]}),` 66`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#6CB6FF"},children:" FORCE_CLOSE"}),e.jsx(n.span,{style:{color:"#F47067"},children:" ="}),e.jsx(n.span,{style:{color:"#6CB6FF"},children:" 4000"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:","})]}),` 67`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#6CB6FF"},children:" DOCUMENT_TOO_LARGE"}),e.jsx(n.span,{style:{color:"#F47067"},children:" ="}),e.jsx(n.span,{style:{color:"#6CB6FF"},children:" 4001"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:","})]}),` 68`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#6CB6FF"},children:" MEMORY_PRESSURE"}),e.jsx(n.span,{style:{color:"#F47067"},children:" ="}),e.jsx(n.span,{style:{color:"#6CB6FF"},children:" 4002"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:","})]}),` 69`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#6CB6FF"},children:" SECURITY_VIOLATION"}),e.jsx(n.span,{style:{color:"#F47067"},children:" ="}),e.jsx(n.span,{style:{color:"#6CB6FF"},children:" 4003"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:","})]}),` 70`,e.jsx(n.span,{"data-line":"",children:e.jsx(n.span,{style:{color:"#ADBAC7"},children:"}"})})]})})}),` 71`,e.jsx(n.figure,{"data-rehype-pretty-code-figure":"",children:e.jsx(n.pre,{tabIndex:"0","data-language":"ts","data-theme":"github-dark-dimmed",children:e.jsxs(n.code,{"data-language":"ts","data-theme":"github-dark-dimmed",style:{display:"grid"},children:[e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#ADBAC7"},children:"document.connections."}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:"forEach"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(({ "}),e.jsx(n.span,{style:{color:"#F69D50"},children:"connection"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:" }) "}),e.jsx(n.span,{style:{color:"#F47067"},children:"=>"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:" {"})]}),` 72`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#ADBAC7"},children:" connection."}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:"sendStateless"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"("}),e.jsx(n.span,{style:{color:"#6CB6FF"},children:"JSON"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"."}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:"stringify"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(forceCloseMessage));"})]}),` 73`,e.jsx(n.span,{"data-line":"",children:e.jsx(n.span,{style:{color:"#ADBAC7"},children:"});"})}),` 74`,e.jsx(n.span,{"data-line":"",children:" "}),` 75`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#ADBAC7"},children:"document.connections."}),e.jsx(n.span,{style:{color:"#DCBDFB"}
75,children:"forEach"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(({ "}),e.jsx(n.span,{style:{color:"#F69D50"},children:"connection"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:" }) "}),e.jsx(n.span,{style:{color:"#F47067"},children:"=>"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:" {"})]}),` 76`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#ADBAC7"},children:" connection."}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:"close"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"({ code, reason });"})]}),` 77`,e.jsx(n.span,{"data-line":"",children:e.jsx(n.span,{style:{color:"#ADBAC7"},children:"});"})})]})})}),` 78`,e.jsx(n.p,{children:"Clients handle this differently from normal disconnects: they donât retry endlessly. They show the reason to the user and let them fix it."}),` 79`,e.jsx(n.h2,{children:"The Local-First UX (No Loading Screen)"}),` 80`,e.jsxs(n.p,{children:["Local-first isnât just offline. Itâs ",e.jsx(n.strong,{children:"instant content on first load"}),"."]}),` 81`,e.jsxs(n.p,{children:["We use ",e.jsx(n.span,{"data-rehype-pretty-code-figure":"",children:e.jsx(n.code,{"data-language":"plaintext","data-theme":"github-dark-dimmed",children:e.jsx(n.span,{"data-line":"",children:e.jsx(n.span,{children:"y-indexeddb"})})})})," so cached content appears immediately. The server sync runs in parallel in the background."]}),` 82`,e.jsx(n.p,{children:"We show content if:"}),` 83`,e.jsxs(n.ol,{children:[` 84`,e.jsx(n.li,{children:"The server is synced"}),` 85`,e.jsx(n.li,{children:"The server is disconnected (offline)"}),` 86`,e.jsx(n.li,{children:"The cache is ready and has content"}),` 87`]}),` 88`,e.jsx(n.p,{children:"That parallel path removes network latency from the critical path. No scary empty screen. No fake âloadingâ for big docs."}),` 89`,e.jsx(n.h2,{children:"Server-Side Editing Without Data Loss"}),` 90`,e.jsx(n.p,{children:"We needed to edit docs on the server while users are typing. Directly writing to the live doc risks stomping edits. We also wanted to reuse Tiptapâs APIs on the server."}),` 91`,e.jsxs(n.p,{children:["The solution was a ",e.jsx(n.strong,{children:"Shadow Y.Doc"}),":"]}),` 92`,e.jsxs(n.ul,{children:[` 93`,e.jsx(n.li,{children:"Clone the live doc into a shadow"}),` 94`,e.jsx(n.li,{children:"Run server edits on the shadow (with a fake DOM)"}),` 95`,e.jsx(n.li,{children:"Compute the delta and merge back into the live doc"}),` 96`]}),` 97`,e.jsx(n.p,{children:"Users keep typing. The server keeps editing. Yjs merges both. No overwrites."}),` 98`,e.jsx(n.figure,{"data-rehype-pretty-code-figure":"",children:e.jsx(n.pre,{tabIndex:"0","data-language":"ts","data-theme":"github-dark-dimmed",children:e.jsxs(n.code,{"data-language":"ts","data-theme":"github-dark-dimmed",style:{display:"grid"},children:[e.jsx(n.span,{"data-line":"",children:e.jsx(n.span,{style:{color:"#768390"},children:"// Shadow Y.Doc pattern for non-destructive server edits"})}),` 99`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#F47067"},children:"const"}),e.jsx(n.span,{style:{color:"#6CB6FF"},children:" base"}),e.jsx(n.span,{style:{color:"#F47067"},children:" ="}),e.jsx(n.span,{style:{color:"#F47067"},children:" new"}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:" Doc"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"();"})]}),` 100`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#DCBDFB"},children:"applyUpdate"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(base, "}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:"encodeStateAsUpdate"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(ydoc));"})]}),` 101`,e.jsx(n.span,{"data-line":"",children:" "}),` 102`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#F47067"},children:"const"}),e.jsx(n.span,{style:{color:"#6CB6FF"},children:" update"}),e.jsx(n.span,{style:{color:"#F47067"},children:" ="}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:" encodeStateAsUpdate"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(base, "}),e.jsx(n.span,{style:{color:"#DCBDFB"},children:"encodeStateVector"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(ydoc));"})]}),` 103`,e.jsxs(n.span,{"data-line":"",children:[e.jsx(n.span,{style:{color:"#DCBDFB"},children:"applyUpdate"}),e.jsx(n.span,{style:{color:"#ADBAC7"},children:"(ydoc, update); "}),e.jsx(n.span,{style:{color:"#768390"},children:"// CRDT merge into live doc"})]})]})})}),` 104`,e.jsx(n.h2,{children:"Slides / Resources"}),` 105`,e.jsxs(n.ul,{children:[` 106`,e.jsx(n.li,{children:e.jsx(n.a,{href:"https://manus.im/share/file/890c3ec7-9dea-4fc5-908a-4a0371055342",children:"Slides"})}),` 107`,e.jsx(n.li,{children:e.jsx(n.a,{href:"https://fosdem.org/2026/schedule/event/TCH8XQ-plane-wiki-local-first-in-production-with-yjs/",children:"Event page"})}),` 108`]}),` 109`,e.jsxs(n.p,{children:["If youâve read my ",e.jsx(n.a,{href:"/blogs/version-history-and-snapshots-in-yjs",children:"version history series"}),", youâll recognize some of the production lessons. The talk w
109as the âcleaned upâ version of those painful learnings."]}),` 110`,e.jsx(n.h2,{children:"If You Take One Thing Away"}),` 111`,e.jsxs(n.ol,{children:[` 112`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Save the binary."})," Y.Doc binary is the source of truth."]}),` 113`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Never overwrite, always merge."})," At the Yjs level and at the storage level."]}),` 114`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Build fallbacks."})," WebSockets die. Servers crash. Users keep typing."]}),` 115`]}),` 116`,e.jsx(n.p,{children:"Thatâs what took us from âworks in demoâ to âsurvives production.â"}),` 117`,e.jsx(n.h2,{children:"Thank You"}),` 118`,e.jsx(n.p,{children:"Huge thanks to the FOSDEM organizers and the Local-First devroom team for a warm, well-run room. The community was kind and curious, and that made it easier to do the scary thing."}),` 119`,e.jsx(n.p,{children:"I also want to thank Kevin and the entire Yjs community. This library is a ridiculous piece of engineering, and itâs the foundation for everything weâve built at Plane."}),` 120`,e.jsx(n.p,{children:"If you want to chat about local-first systems, Yjs, or collaborative editors, Iâm easy to find:"}),` 121`,e.jsxs(n.ul,{children:[` 122`,e.jsxs(n.li,{children:["X/Twitter: ",e.jsx(n.a,{href:"https://x.com/Palanikannan_M",children:"https://x.com/Palanikannan_M"})]}),` 123`,e.jsxs(n.li,{children:["GitHub: ",e.jsx(n.a,{href:"https://github.com/Palanikannan1437",children:"https://github.com/Palanikannan1437"})]}),` 124`,e.jsxs(n.li,{children:["LinkedIn: ",e.jsx(n.a,{href:"https://www.linkedin.com/in/palanikannan-m-021822200/",children:"https://www.linkedin.com/in/palanikannan-m-021822200/"})]}),` 125`,e.jsxs(n.li,{children:["Email: ",e.jsx(n.a,{href:"mailto:[email protected]",children:"[email protected]"})]}),` 126`]}),` 127`,e.jsx(n.p,{children:"â Palanikannan"})]})}function p(s={}){const{wrapper:n}={...o(),...s.components};return n?e.jsx(n,{...s,children:e.jsx(i,{...s})}):i(s)}function t(s,n){throw new Error("Expected component `"+s+"` to be defined: you likely forgot to import, pass, or provide it.")}export{p as default,h as frontmatter};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.