Skip to content

Live Collaboration

Pummelchen edited this page Sep 16, 2026 · 4 revisions

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.

The flow

  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)
  1. 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.
  2. 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.
  3. Leave Live Mode — saves the current state once, for everyone, then detaches you. You keep working on your own copy.

Two channels, on purpose

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.

Rules that keep it safe

  • 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.

Presence

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.

Try it

  1. Open RoomCAD in two different browsers (or one normal + one private window) and log into both.
  2. Save in the first one — Live needs a server-side design.
  3. Within ~3 s, Join Live turns green in both. Click it in both.
  4. Drag a bed in one window; watch it move in the other.
  5. Click Leave Live Mode. It saves once for everyone, and the status bar reads "Saved for everyone · left Live Mode · working on your own".

If updates do not arrive

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.

Clone this wiki locally