A lightweight, serverless-first web application for conducting technical interviews with real-time code collaboration, drawing canvas, and messaging.
- Real-Time Code Editor - Collaborative code editing with syntax highlighting for 15+ languages
- Drawing Canvas - Freehand drawing, shapes, diagrams with zoom/pan support (mouse wheel & touch gestures)
- Messaging System - Built-in chat with message history and participant list
- Multi-Participant - Support for up to 10 participants per session
- Peer-to-Peer - WebRTC-based communication, no data stored on servers
- PDF Export - Export session content (code, drawings, messages) to PDF
- Session Sharing - Simple URL-based session sharing with name entry
- Host Management - Automatic host transfer when the host leaves
- Tab Sync - Code/Diagram tab state synced between all participants
- Offline Persistence - Auto-saves to browser storage
# Install dependencies
npm install
cd server && npm install && cd ..
# Start both signaling server and frontend
npm run dev:allThen open http://localhost:3000 in your browser.
- Enter your name - A modal prompts for your display name (required)
- Join the session - The first user becomes the host
- Share the URL - Share the session URL with participants
- View participants - Participant list shows all connected users with host indicator
- Select language - Choose from JavaScript, TypeScript, Python, Java, Kotlin, C++, C, C#, Go, Rust, Ruby, Swift, Scala, PHP, SQL
- Type code - Changes sync automatically with operational transformation
- Cursor tracking - See your partner's cursor position in real-time
- Tab sync - Switching tabs (Code/Diagram) syncs to all participants
- Drawing Tools (emoji-based toolbar):
- ✏️ Pen - Freehand drawing
- ➖ Line - Straight lines
- ⬜ Rectangle - Draw rectangles
- ⭕ Circle - Draw circles/ellipses
- 🎨 Color picker - Choose any color
↕️ Brush size - Adjust line thickness (1-20px)- 🗑️ Clear - Wipe the canvas (synced to all)
- 1:1 Reset zoom - Reset zoom and pan to default
- Zoom/Pan:
- Mouse wheel - Zoom in/out centered on cursor
- Pinch gesture - Two-finger zoom on touch devices
- Two-finger drag - Pan around the canvas
- Zoom range: 25% to 400%
- Type messages in the chat panel
- Messages are synced and persisted
- Each message shows sender role and timestamp
- Host (👑) - Session creator with administrative privileges
- Participants - All other users in the session
- Host Transfer - When the host leaves, the longest-connected participant becomes the new host
- Name Entry - All participants must enter their name before joining
- Participant List - View all connected users in the collapsible panel
Click "Export PDF" to download a document containing:
- All code with syntax highlighting preserved
- Canvas drawing as image
- Complete message history
- Session metadata
- TypeScript + React + Vite - Type-safe component-based UI with fast build tooling
- Zustand - Lightweight state management
- Prism.js - Syntax highlighting
- jsPDF - PDF generation
- WebRTC - Peer-to-peer data channels
The application uses Operational Transformation (OT) for conflict-free concurrent editing:
- Local changes are converted to operations (retain, insert, delete)
- Operations are sent to peer via WebRTC DataChannel
- Concurrent operations are transformed to maintain consistency
- Both peers converge to identical state
-
Direct Connection (10s timeout)
- Uses STUN servers to discover public IP
- Attempts direct P2P connection
-
Relay Fallback (15s timeout)
- Uses TURN servers when direct connection fails
- Works through symmetric NATs
-
Signaling Server
- Optional Socket.IO server for WebRTC signaling
- Falls back to localStorage signaling for same-browser testing
- localStorage - Session data, code, messages
- IndexedDB - Canvas images (large binary data)
- No server-side storage
| Browser | Version | Status |
|---|---|---|
| Chrome | 80+ | Full Support |
| Firefox | 75+ | Full Support |
| Safari | 14+ | Full Support |
| Edge | 80+ | Full Support |
| Chrome Android | Latest | Full Support |
| iOS Safari | 14+ | Full Support |
- WebRTC (RTCPeerConnection, RTCDataChannel)
- localStorage & IndexedDB
- Canvas 2D API
- Clipboard API (for URL sharing)
- WebSocket (for signaling server)
duocode/
├── index.html # Vite entry HTML
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript configuration
├── src/
│ ├── main.tsx # React entry point
│ ├── App.tsx # Root component
│ ├── styles.css # All styles (theme variables + layout)
│ ├── components/ # React components by feature
│ ├── hooks/ # Custom React hooks (sync, persistence)
│ ├── stores/ # Zustand state stores
│ └── services/ # Business logic (OT, WebRTC, persistence)
├── public/ # Static files (privacy, terms, llms.txt)
├── tests/
│ ├── unit/ # Vitest unit tests
│ └── e2e/ # Playwright E2E tests
└── server/
├── server.js # Signaling server
├── package.json # Server dependencies
└── Dockerfile # Container configuration
| Variable | Default | Description |
|---|---|---|
PORT |
3001 | Signaling server port |
ALLOWED_ORIGINS |
* | CORS allowed origins (comma-separated) |
VITE_GA_ID |
(none) | Google Analytics measurement ID (build-time, optional) |
Set VITE_GA_ID before running npm run build to enable Google Analytics. If omitted, the GA script is not loaded. See .env.example for a template.
The deploy job requires these GitHub Actions secrets (Settings > Secrets > Actions):
| Secret | Description |
|---|---|
DEPLOY_SERVER_SSH_PRIVATE_KEY |
SSH private key for server access |
DEPLOY_SERVER_NAME_HOST |
SSH destination, e.g. root@myserver.com |
Edit src/services/connection-manager.ts to customize:
const options = {
stunServers: [...], // Custom STUN servers
turnServers: [...], // TURN servers for relay
directConnectionTimeout: 10000,
relayConnectionTimeout: 15000,
qualityCheckInterval: 2000,
maxReconnectAttempts: 5
};DuoCode has comprehensive test coverage with 1063 unit tests across 46 test files.
Unit Tests (Vitest)
# Run tests once
npm run test
# Run tests in watch mode
npm run test:watch
# Run tests with UI
npm run test:ui
# Generate coverage report
npm run test:coverageE2E Tests (Playwright)
npm run test:e2eTest Coverage Areas:
- Operational transformation (OT) for code sync
- Session management and ID generation
- WebRTC connection handling
- Canvas undo/redo and zoom/pan sync
- Message queuing and persistence
- Device detection and responsive layout
- Error handling and user feedback
- Signaling client communication
# Frontend only
npm run dev
# Signaling server only
npm run server
# Both (parallel)
npm run dev:all- Both on same network - Direct P2P should work
- Different networks - May need signaling server + TURN relay
- Corporate firewall - Configure TURN server with TCP fallback
- Refresh the page - Resets local state
- Check console - Look for WebRTC errors
- Verify connection status - Should show "Connected"
- Ensure both peers are connected
- Check if canvas element is visible
- Verify IndexedDB is not full
See CONTRIBUTING.md for setup instructions and development workflow.
This project was built with mAIstro and Claude Code. mAIstro is a small CLI for orchestrating Claude Code tasks when one-shot prompts and limited context aren't enough.
Free to use for personal and commercial purposes. Modification, redistribution, and resale are not permitted. See LICENSE for details.
Copyright (c) 2026 DreamTeam Mobile