APP-70Recommended
Show presence from short-lived state
Keep cursors, selections and who is here out of the stored document. Each client renews its presence at least every 15 seconds, and presence expires after 30 seconds without renewal.
How to do it
Presence changes many times a second, and storing it fills the document and its history. Send it as separate state that only lives in memory. Yjs calls this awareness, and it marks a peer offline after 30 seconds without an update. Clear the local state when the page closes, so others see the person leave at once. Show each person's name next to their cursor, so colour is not the only clue. Share presence only with people who may open the document, and drop dead connections as Send heartbeats and drop dead connections says.
Examples
TypeScript example
const provider = new WebsocketProvider('wss://collab.example.com', `doc-${documentId}`, doc);
provider.awareness.setLocalStateField('user', { name: currentUser.name });
provider.awareness.on('change', () => renderPresence([...provider.awareness.getStates().values()]));
addEventListener('pagehide', () => provider.awareness.setLocalState(null));
Python example
PRESENCE_TTL_SECONDS = 30
last_seen: dict[tuple[str, str], float] = {}
def renew_presence(document_id: str, user_id: str, now: float) -> None:
last_seen[(document_id, user_id)] = now
def who_is_here(document_id: str, now: float) -> list[str]:
return [user for (doc, user), seen in last_seen.items()
if doc == document_id and now - seen < PRESENCE_TTL_SECONDS]
How to check
A test opens the document in two browsers and cuts the network of one. Within 35 seconds, the other no longer shows that person. The stored document holds no cursor or selection fields.
Related rules
- API-71 Send heartbeats and drop dead connections in API design
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/app-ui/live-collaboration/presence-expires">Agent Wranglers web app interface playbook</a>. In code you write, add a comment next to it with the same link.