Production-oriented collaborative coding workspace: Yjs CRDTs over WebSockets, CodeMirror 6, Express orchestration, optional Piston (Docker) execution, optional Redis fan-out, and IndexedDB client recovery. The UI follows a cyber-dark / glass aesthetic with resizable VS Code–like panels.
| Area | What you get |
|---|---|
| Collaboration | Shared Y.Map of files (Y.Text per file), chat (Y.Array), presence via Awareness, remote carets/selections (y-codemirror.next). |
| Workspace | File explorer, editor tabs, per-room shared language metadata, multi-file create/rename/delete. |
| Presence | Online list, chat vs code typing indicators, room activity feed, REST ping to /api/health, connection status chip. |
| Execution | POST /api/execute → Piston when available, local fallback; configurable timeouts and memory limits on Piston requests. |
| Persistence | Server: debounced file snapshots + optional Redis incremental channel for multi-node. Client: IndexedDB (y-indexeddb) for refresh resilience. |
| AI (optional) | POST /api/ai/chat proxies OpenRouter / OpenAI-compatible APIs; keys stay on the server. |
| Extras | Session timeline checkpoints, whiteboard (Y.Array strokes), interview timer route, floating AI assistant. |
flowchart LR
subgraph clients [Browsers]
A[React + Vite]
B[CodeMirror + y-codemirror]
C[IndexedDB persistence]
end
subgraph node [Node collab server]
W[WebSocket + setupWSConnection]
P[setPersistence file/Redis]
E[Express /api]
end
subgraph engines [Execution]
Pi[Piston Docker]
Lo[Local runtimes fallback]
end
A --> W
B --> W
C --> A
W --> P
A --> E
E --> Pi
E --> Lo
P --> R[(Redis optional)]
- Each room is a WebSocket path
/:roomIdbacked by one in-memoryY.Docon the node (merged via Yjs). - Persistence writes merged binary state to
.yjs-data/and optionally mirrors updates on Redisyjs:upd:<roomId>for horizontal scale-out. - The client uses
WebsocketProvider+IndexedDBso a refresh can hydrate from disk before the socket catches up.
Conflict model: edits merge with Yjs CRDTs (not classical OT). See
client/src/collab/crdtNotes.jsanddocs/system-design.md.
- Frontend: React 19, Vite 7, Zustand, react-resizable-panels, react-router, CodeMirror 6, y-codemirror.next, lucide-react
- Realtime: yjs, y-websocket,
@y/websocket-server - Backend: Express 5, ws,
setupWSConnection, optional ioredis - Run: Piston (
docker-compose) or local Node/Python/GCC where installed
git clone <repo>
cd collaborative-editor
npm install
npm run install-client # or: cd client && npm installDevelopment (API proxied through Vite):
npm run dev
# Client: http://localhost:5173 → proxies /api → :1234
# Collab WS: ws://localhost:1234/<roomId>Execution engine (recommended):
docker compose up -d piston
# Install runtimes on Piston as needed, see server/services/pistonService.jsOptional Redis (multi-instance Yjs fan-out + shared snapshot key):
docker compose up -d redis
export REDIS_URL=redis://127.0.0.1:6379
npm run serverOptional AI:
export OPENROUTER_API_KEY=sk-or-...
# optional: OPENROUTER_MODEL=openai/gpt-4o-mini| Variable | Purpose |
|---|---|
PORT |
HTTP + WS port (default 1234) |
YJS_PERSISTENCE_DIR |
Directory for .ybin snapshots (default ./.yjs-data) |
REDIS_URL |
Enables Redis publish + subscribe for cross-node Y updates |
PISTON_URL |
Piston base URL (default http://localhost:2000) |
PISTON_RUN_TIMEOUT_MS / PISTON_COMPILE_TIMEOUT_MS / PISTON_MEMORY_LIMIT_BYTES |
Execution guardrails |
OPENROUTER_API_KEY |
Server-side AI proxy |
VITE_WS_URL / VITE_SERVER_ORIGIN |
Client override when not using Vite proxy |
npm run build
NODE_ENV=production npm run startServe static files from client/dist with your reverse proxy, or embed static middleware (not included by default).
server/
index.js # Express + WS + persistence bootstrap
persistence/yjsPersistence.js
routes/api.js # execute + health
routes/ai.js # OpenRouter-compatible proxy
services/pistonService.js
client/src/
components/ # Editor, explorer, presence, AI, whiteboard, …
hooks/useYjsProvider.js
collab/ # CRDT notes + replay helpers
config/api.js # apiUrl / wsUrl
- Yjs sends binary incremental updates; awareness fields are debounced for typing indicators.
- REST ping uses
/api/healthas a coarse latency sample (not WS RTT). - Bundle size: consider lazy-loading AI and whiteboard if you need a smaller initial payload.
- WebRTC voice/video with dedicated signaling service
- Stricter interview spectator/read-only enforcement in the editor
- Automated benchmarks in CI
ISC (see repository). Third-party libraries retain their own licenses.