
Session
A real-time collaborative coding environment for pair programming and technical interviews — with live code sync, LiveKit video calls, AI assistance, and Redis + BullMQ room lifecycle hardening.
Timeline
Role
Status
In-progressTechnology Stack
Overview
Session started as a simple idea: coding together should feel as natural as sitting next to someone. It's a space for pair programming, interviews, and late-night debugging sessions where you can write, run, and talk through code at the same time.
I built it to handle the messy parts for you — keeping code in sync, keeping calls stable, and cleaning up empty rooms without you ever noticing.
Coding together in real time
At its core, Session is a shared editor. It uses Yjs under the hood so edits merge cleanly even when two people type at once.
- You see each other's changes instantly, no refresh needed
- It's the Monaco editor, so it feels like VS Code — familiar shortcuts, multi-language support for JavaScript, TypeScript, Python, Java, C++, Go, HTML, CSS, and more
- You can see where your partner's cursor is, which helps a lot when you're talking through a bug
Video calls that stay out of the way
Early on the video call was just a little fixed box in the corner — a basic LiveKit grid. It worked, but it got in the way when you were trying to code.
So I rebuilt it from scratch as neoVideoCall (frontend/src/components/editor/neoVideoCall.tsx). Now it's a floating layer you can drag anywhere, and it remembers where you left it.
It has three modes depending on what you're doing: a tiny minimal pill when you just want to know who's around, a preview when you want faces visible, and a full focus view for discussions or screen shares.
Here's a quick look at a live call with screen sharing — this is the rebuilt floating layer in action, not the old corner box.
A few details I'm happy with:
- You can pin someone to keep them front and center, and it clears itself sensibly if they leave. In focus mode with a few people, it switches to a balanced grid on its own.
- When someone shares their screen, the call jumps to focus automatically — you can dismiss it if you don't care.
- Each tile shows who's speaking, who's muted, and mirrors your own camera so it doesn't feel flipped.
- The top bar stays in sync too. Instead of a static "In Call" label, it shows things like who is speaking, if you're muted, or how many people are in the call.
- Dragging feels right on both mouse and touch, with different thresholds so you don't accidentally click when you meant to drag. It also handles resizing, mode switches, and small phone screens without jumping around. There's a call timer, mic/camera/screen controls, and everything cleans up properly on disconnect.
The old VideoCall.tsx is gone, along with a couple of dead context wrappers I wasn't using anymore.
A bit of AI help when you need it
There's a chat panel built in for when you get stuck. You can ask for an explanation, get suggestions on a snippet, or have it generate a starter problem and boilerplate so you don't start from a blank file. It's powered by Google's Gemini models through the GenAI SDK.
I try to keep it contextual — it's there to unblock you, not to take over the session.
Designed to feel calm
I wanted Session to feel good during long sessions, so it's dark-mode first with soft translucent panels and gentle Framer Motion transitions. Nothing flashy, just enough polish that the UI fades into the background while you code.
Rooms that clean up after themselves
This was the less visible but most important work. Rooms are created and abandoned all the time, and I needed a reliable way to delete empty ones without breaking everything if Redis had a bad moment.
The original setup shared one Redis connection across the BullMQ queue, worker, rate limiter, and test script. BullMQ really doesn't like that, and any Redis hiccup — TLS issues on Redis Cloud, reconnects, downtime — could hang webhooks or crash health checks.
Here's what I changed:
- Added a proper connection factory in
backend/src/config/redis.ts. Each part of the app now gets its own named connection with a 10s connect timeout, capped retry backoff, TLS when needed, and clear lifecycle logs. Passwords are redacted in logs, and ifREDIS_URLis missing it falls back to localhost with a loud warning. There are also small helpers to check if Redis is ready and to time-box any Redis call to 5 seconds so nothing hangs forever. - Split into three isolated clients: one for the app itself, one for the room-deletion queue, and one for the worker. They never share.
- The queue (
roomDeletion.queue.ts) now retries 3 times with exponential backoff and caps how many completed/failed jobs it keeps. Scheduling a deletion first clears any stale job for that room, and duplicate schedules are treated as harmless. Canceling works the same way and never throws. - The worker runs with a concurrency of 5 and is careful about races — if a room is already gone (404), it just moves on instead of retrying in a loop.
- The Liveblocks webhooks (
userEntered/userLeft) are fail-open. If someone joins, we try to cancel the pending deletion. If the last person leaves, we schedule deletion 15 minutes out. If Redis is down, we log it and still return 200 so presence never breaks. /healthnow reports Redis status but always returns 200, so an orchestrator won't restart the service over a brief Redis blip — clients just check thereadyflag.- Shutdown is graceful. On SIGTERM/SIGINT the server stops accepting requests, then closes the worker, queue, and Redis connections cleanly.
In short: calls feel lighter on the frontend, and rooms are much safer on the backend even when infrastructure misbehaves.
Tech Stack
Frontend
- React 19 + Vite: fast dev loop and responsive UI
- Tailwind CSS v4 + Framer Motion: styling and subtle animation
- LiveKit: audio, video, and screen-share tracks with speaking indicators
- Monaco Editor: the actual code editing surface
Backend
- Node.js + Express: API and webhook handling
- Liveblocks webhooks: I use presence events to know when to keep or delete a room
- IORedis + BullMQ: delayed deletion jobs with retries and bounded retention
- Google GenAI SDK: Gemini-powered chat and bootstrapping
How it's put together
Request Flow:
Routes → Controllers → Services → Database/External APIs
Realtime Flow:
Editor (Yjs + Monaco) <-> WebSocket
neoVideoCall <-> LiveKit (audio/video/screenshare)
TopBar <- CallStatus { count, speakingName, muted }
Cleanup Flow:
Liveblocks webhook (userEntered/userLeft)
→ cancelRoomDeletion / scheduleRoomDeletion (15 min delay)
→ BullMQ Queue (isolated Redis conn)
→ Worker (concurrency 5, 404-idempotent) → deleteRoom
Routes are grouped by feature, business logic lives in services rather than controllers, and errors are handled in one place. Every Redis call has a timeout, so a slow dependency can't take down the whole request path.