[Shihab's Blog]
Back to Root
EST: 2 min read

How I Built LetX: Real-Time Collaborative LaTeX

I built LetX, a real-time collaborative LaTeX editor, using CRDTs for conflict-free editing and sandboxed Docker compile. Here's the full architecture.

#letx#crdt#go#systems

I built LetX because every time I co-authored a paper, Overleaf dropped our edits, timed out on compile, or merged in the wrong version. The core problem: real-time document collaboration under LaTeX's strict, stateful compilation model.

The CRDT decision

The first design question was how to merge concurrent edits without conflicts. Operational Transformation (OT) requires a central server to linearize all operations. CRDTs don't — each client converges independently.

I chose a sequence CRDT (specifically a variant of RGA — Replicated Growable Array) for the text buffer. Every character insertion is globally unique via a vector clock + site ID pair. No server arbitration needed for the document state.

type Op struct {
    ID      VClock
    SiteID  string
    Pos     CRDTPos   // (leftOrigin, rightOrigin)
    Char    rune
    Deleted bool
}

Conflicts resolve deterministically by comparing site IDs when two insertions target the same position. The CRDT layer runs in Go on the server and TypeScript on the client — same algorithm, both sides.

Sandboxed compile

LaTeX compilation is dangerous: \write18 lets you execute arbitrary shell commands. Every compile job runs in a Docker container with:

  • Filesystem isolated via --read-only + tmpfs for /tmp
  • Network disabled (--network none)
  • Memory cap: 512 MB, 30s CPU timeout
  • PID limit: 64
docker run --rm \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m \
  --network none \
  --memory 512m \
  --pids-limit 64 \
  letx-latex-runner \
  pdflatex -interaction=nonstopmode main.tex

Output PDF streams back via a signed S3 URL with a 5-minute TTL.

What I'd change

The CRDT serialization is verbose — each operation carries its full vector clock. For documents over ~50k characters, the in-memory op log becomes the bottleneck. Next version will compact via periodic snapshots with a tombstone-pruned state vector.


Try it at letx.app. Built by Shihab Shahriar AntorShahriar Labs.