CodeQuorum is a browser-based, multi-file collaborative IDE. Multiple developers share a room and edit the same workspace — code files and Jupyter notebooks — at the same time. Every keystroke syncs in real time, remote cursors and selections appear inline with each collaborator's name, and edits merge conflict-free even when people type in the same spot simultaneously.
It's built on a CRDT (Yjs) rather than naive last-write-wins broadcasting, so concurrent edits never clobber each other or corrupt the document — the same approach used by production collaborative editors.
Real-time collaboration
- Conflict-free editing — Yjs CRDTs; concurrent edits always converge.
- Live presence & cursors — see who's in the room, where they're typing, and which file they're in, color-coded per user.
- Follow mode — click a collaborator to follow their cursor and file switches live (à la VS Code Live Share).
- Spotlight / Present — pull everyone in the room to follow you (à la Figma).
- Inline comments — comment on a selection; threaded, resolvable, and CRDT-anchored so they stick to the code as it moves (à la Google Docs).
- Shareable rooms — create a room, send the link, collaborate. Works anonymously.
- Collaborative undo/redo — your undo reverts only your changes.
Accounts & access control
- Optional accounts — email/password (scrypt) or GitHub OAuth; sign up for a dashboard of your rooms. Anonymous link-sharing still works.
- Public / read-only / private rooms — owners control who can view and who can edit.
- Server-enforced authorization — private rooms reject non-members and read-only is enforced on the server, never just hidden in the UI.
- Invite members by email with editor or viewer roles.
IDE productivity
- Project-wide search (
Ctrl/⌘+Shift+F) — search across every file, grouped results, jump-to-line. - Version history — automatic + manual snapshots of a room; restore any past version into a new room.
- Command palette, find & replace, tabbed editor, folder tree, fork, templates (see below).
Multi-file workspace ("full codebases")
- File explorer with folders — organize files into a tree using
/paths; create, rename, delete. - Tabbed editor — open multiple files at once; your open tabs persist across reloads.
- 14 languages, syntax-highlighted — JS, TS, JSX, Python, HTML, CSS, JSON, Markdown, C/C++, Java, Rust, PHP, SQL, XML — auto-detected from the file extension.
- Upload & export — drag files into a room, download a single file, or export the entire workspace as a
.zip.
Collaborative Jupyter notebooks (.ipynb)
- Real notebook UI — code and markdown cells; add, move, and delete cells.
- Executable cells, JS or Python — switch the kernel per notebook; run cells (▶ / Shift+Enter) with output inline. Python runs via a self-hosted Pyodide runtime with a persistent shared kernel (variables carry across cells, like Jupyter).
- Live markdown rendering — rendered with sanitized HTML (XSS-safe for shared input).
- Import & export real
.ipynb— interoperates with Jupyter / nbviewer; outputs are preserved on round-trip. - Every cell is collaborative — multiple people edit different cells of the same notebook at once.
Run, preview & productivity
- Run JavaScript — executes in a sandboxed Web Worker with captured console output and an infinite-loop timeout (
Ctrl/⌘+Enter). - Live HTML preview with a console — renders in a sandboxed, locked-down iframe (its own CSP), inlining sibling CSS/JS from the workspace, and captures the page's
console/errors into a console pane (à la CodeSandbox). - Markdown preview — rendered side-by-side for
.mdfiles. - Starter templates — scaffold a room as a web page, JS script, or notebook (à la CodePen).
- Fork a workspace — one-click duplicate any room into your own copy (à la Replit).
- Command palette (
Ctrl/⌘+P) — fuzzy go-to-file and run commands. - Find & replace (
Ctrl/⌘+F) — full CodeMirror search. - In-room chat — talk to collaborators without leaving the editor; messages persist with the room.
Platform
- Persistent rooms — workspaces and chat are saved to disk and restored after a server restart.
- Resilient connections — automatic reconnection with a live status indicator.
- Polished, framework-free UI — toasts and modal dialogs (no native
alert/prompt), keyboard-driven, responsive on desktop and mobile.
| Layer | Technology |
|---|---|
| Editor | CodeMirror 6 |
| Realtime sync | Yjs (CRDT) + y-codemirror.next |
| Transport | Socket.IO (WebSocket, with polling fallback) |
| Server | Express on Node.js 22+ |
| Database | node:sqlite (built-in, parameterized queries, versioned migrations) |
| Auth | scrypt password hashing + JWT cookies, GitHub OAuth; zod validation |
| Code execution | sandboxed Web Worker (JS) · self-hosted Pyodide (Python, shared kernel) |
| Security | helmet (CSP/HSTS/…), express-rate-limit |
| Notebooks | marked + DOMPurify; JSZip for export |
| Build / DX | esbuild bundler · TypeScript typed protocol · ESLint · node:test · GitHub Actions CI · Docker |
The client is bundled into one self-contained file and the database is embedded; the Python runtime is self-hosted too — so no third-party CDN is required.
- Node.js ≥ 22 (uses the built-in
node:sqlite)
git clone https://github.com/nishantkluhera/CodeQuorum.git
cd CodeQuorum
npm install
cp .env.example .env # then set JWT_SECRET
npm startnpm start bundles the client and launches the server. Open
http://localhost:3000 — create a room as a guest, or sign up for a dashboard
with private/read-only rooms.
To collaborate, open the room URL in a second browser/tab (or send it to a friend) and start typing — changes appear instantly in both.
| Variable | Default | Purpose |
|---|---|---|
PORT |
3000 |
HTTP port |
JWT_SECRET |
dev fallback (required in prod) | signs session cookies |
NODE_ENV |
development |
production enables Secure cookies + requires JWT_SECRET |
DB_PATH |
./data/codequorum.db |
SQLite database file |
Browser A ──┐ ┌── Browser B
CodeMirror │ Yjs update (binary) │ CodeMirror
│ yCollab │ ───────────────────────────► │ yCollab │
Yjs doc ────┤ Socket.IO ├──── Yjs doc
awareness ──┘ ◄───────────────────────── └── awareness
│
┌────▼──────────┐
│ Server │ • REST: auth + room management
│ Express + IO │ • authoritative Yjs doc per room
│ │ • authz: private/read-only enforced here
└────┬──────────┘ • debounced persistence
│
SQLite (node:sqlite)
users · rooms · members · documents(blob)
Each client holds a local Yjs document and connects its active editor to it via
yCollab. Local edits are encoded as compact binary updates and sent over
Socket.IO. The server keeps an authoritative Yjs document per room: it merges
every incoming update, rebroadcasts it, and hands the full current state to
late joiners. Cursor/name info travels over the Yjs awareness protocol on the
same channel. Document state is persisted to the SQLite documents table
(debounced) so rooms survive restarts; idle empty rooms are evicted from memory.
Authorization is enforced server-side. On join, the server resolves the
caller's role from the room's visibility and membership; private rooms reject
non-members, and a viewer's edits are dropped on the server — the client UI is
never trusted to gate access.
The whole workspace lives in that one CRDT doc, so multi-file and notebooks need no special server support — the server just relays the doc:
- a
filesY.Map holds file metadata (id → {name, kind, language}), - each code file's body is a Y.Text (
content:<id>), - each notebook is a Y.Array of cells (
cells:<id>), where each cell is a Y.Map with its own Y.Text source.
Switching files re-binds a single editor to that file's shared type; notebook
cells each bind their own editor. See src/server.js for the
wire protocol and client/workspace.js for the model.
CodeQuorum/
├── build.js # esbuild bundler for the browser client
├── src/
│ ├── server.js # Express + Socket.IO; serves API + realtime + authz
│ ├── api.js # REST API: auth + room management (zod-validated)
│ ├── auth.js # password hashing, JWT cookies, middleware
│ ├── db.js # node:sqlite connection + versioned migrations
│ ├── repo.js # data access (parameterized queries) + role logic
│ └── rooms.js # per-room Yjs docs, DB persistence, eviction
├── client/ # browser ES modules (bundled by esbuild)
│ ├── main.js # app entry: doc/socket/awareness, tabs, permissions
│ ├── workspace.js · codeview.js · notebook.js · ipynb.js
│ ├── runner.js # sandboxed JS run + HTML/markdown preview
│ ├── pyrun.js # Python execution via the self-hosted Pyodide worker
│ ├── templates.js # starter scaffolds (web / node / notebook)
│ ├── chat.js · palette.js · ui.js · editor.js · icons.js
├── public/
│ ├── index.html · landing.js # auth-aware landing + dashboard
│ ├── room.html · styles.css # the IDE
│ ├── pyworker.js # Pyodide Web Worker (persistent kernel)
│ └── dist/bundle.js # generated client bundle (git-ignored)
├── shared/
│ └── protocol.ts # typed wire-protocol + domain contract (type-checked)
├── test/
│ ├── rooms.test.js # room/CRDT + DB persistence
│ ├── auth.test.js # password hashing + room access-control roles
│ └── ipynb.test.mjs # .ipynb parse/serialize round-trip
├── scripts/smoke-collab.js # end-to-end two-client collaboration test
├── Dockerfile · docker-compose.yml · .github/workflows/ci.yml
└── SECURITY.md # threat model
| Command | Description |
|---|---|
npm start |
Build the client and start the server |
npm run dev |
Rebuild on change + run the server |
npm test |
Run the unit/integration test suite (node:test) |
npm run typecheck |
Type-check the shared protocol (tsc --noEmit) |
npm run lint |
Lint with ESLint |
With the server running (PORT=3010 npm start), in another shell:
URL=http://localhost:3010 node scripts/smoke-collab.jsTwo clients join one room (real multi-file model) and assert that a late joiner sees existing files and contents, edits propagate both ways, concurrent file creation converges, chat syncs, and the workspaces end up identical.
CodeQuorum runs user-supplied code and renders user-supplied HTML/Markdown, so
isolation is a first-class concern. Highlights: server-side authorization,
scrypt+JWT auth, parameterized SQL, zod validation, rate limiting, a tailored
helmet CSP, JS execution in a timed Web Worker, HTML preview in a
sandboxed iframe, and DOMPurify-sanitized markdown. Full threat model:
SECURITY.md.
Containerized — node:sqlite means no external database to provision.
echo "JWT_SECRET=$(node -e "console.log(require('crypto').randomBytes(48).toString('hex'))")" > .env
docker compose up --build # → http://localhost:3000, data persisted in a volumeCI (GitHub Actions) runs lint, type-check, tests, a build, and a production
npm audit on every push. The Docker image deploys as-is to Fly.io, Render,
Railway, or any container host.
- Python/notebook execution via Pyodide (JS execution & HTML preview already ship)
- Horizontal scale: Socket.IO Redis adapter + Postgres (swap the
repo/roomslayer) - OAuth sign-in, email verification, password reset
- Completing the TypeScript migration from
shared/protocol.tsoutward
Distributed under the MIT License. See LICENSE for details.
