Repository navigation
Live Collaboration
Two people can edit the same design at the same time. RoomCAD makes this deliberate: you are never dragged into a shared session by surprise, and you never leave one without saving for everyone.
someone else is in the project
│
▼
┌──────────────────┐ click ┌─────────────┐ click ┌──────────┐
│ Join Live (green)│ ─────────► │ Live Active │ ─────────────► │ your own │
└──────────────────┘ └─────────────┘ Leave Live │ copy │
appears only when edits sync (saves for └──────────┘
a teammate is present both ways everyone)
- Join Live appears — pulsing green — only when another browser session is present and your design came from the server. Until you click it, nothing is shared.
- Live Active — every edit you make is pushed to the others within about 150 ms, and theirs arrive the same way. No versions are created.
- Leave Live Mode — saves the current state once, for everyone, then detaches you. You keep working on your own copy.
| Saves | Live drafts | |
|---|---|---|
| Endpoint | POST /api/save |
POST /api/live/<name> |
| Stored in | SQLite, a new version each time | server memory only |
| Survives a restart | yes | no — they are drafts |
| Broadcast to watchers | yes | yes (flagged live: true) |
Both arrive over the same Server-Sent Events stream, GET /api/watch/<name>.
A new watcher gets the saved room immediately, then the current unsaved draft
if there is one — so you join mid-edit already in sync.
A stream is not free: it holds a thread, a socket and a queue for as long as it
is open, so the number of them is capped. MAX_WATCHERS_TOTAL (256) protects
the process; MAX_WATCHERS_PER_SESSION (16) stops one person opening streams in
a loop and starving everyone else — sixteen is one per tab with room for the
reconnects a dropped connection makes. Past either cap the server answers 503
with the usual {"error": ...} envelope, before any event-stream headers go
out, and registers nothing, so a refused stream cannot take a slot with it.
- You must save first. Joining with unsaved local edits is refused ("Save your local changes before joining Live"), so your work can never be silently replaced by the shared draft.
- Drafts wait for you. A teammate's draft that arrives before you join is held, not applied. Clicking Join Live adopts it — so joining picks up their work instead of overwriting it.
- Your own edits never bounce back. Each tab has a client id and ignores its own echo.
- An active drag is never interrupted. Remote updates are dropped while you are dragging something.
- Version-matched. A draft for a different saved version than yours is ignored.
- A stale edit is refused, not merged. Every publish carries the sequence number it was built on. If the room has moved on since — a teammate saved, or published after you — the server rejects the edit and hands back the room as it actually is, and the app tells you the change needs making again. That is deliberate: accepting it would replace everyone else's newer work with your older picture of the room, which is how an afternoon's design disappears in one click. Losing one edit and being told so is the lesser harm.
- Drift is checked, not assumed. With nothing unpublished of your own, the app sends a digest of what it holds every couple of seconds; if the room has moved on it takes the server's copy. A dropped update therefore heals itself instead of leaving the two sides quietly diverged for the rest of the session.
- Last write wins. There is no locking, no CRDT and no merge — this is built for two or three people on a private project, not for fifty.
Every browser polls GET /api/status every 3 seconds. The server counts the
session tokens it has seen in the last 30 seconds and returns
{"count": 2}. That number is what makes Join Live appear, and the same
round-trip drives the latency dot next to the version in the footer.
- Open RoomCAD in two different browsers (or one normal + one private window) and log into both.
- Save in the first one — Live needs a server-side design.
- Within ~3 s, Join Live turns green in both. Click it in both.
- Drag a bed in one window; watch it move in the other.
- Click Leave Live Mode. It saves once for everyone, and the status bar reads "Saved for everyone · left Live Mode · working on your own".
The reverse proxy must not buffer the event stream. Caddy needs
flush_interval -1 on the /api/* proxy, and encode gzip must stay on the
static handle rather than at site level — at site level it wraps this stream
too. See Troubleshooting.