Skip to content

feat(realtime): accurate in-file presence + collaborative-caret polish#5965

Open
waleedlatif1 wants to merge 2 commits into
realtime-roomsfrom
realtime-rooms-presence-polish
Open

feat(realtime): accurate in-file presence + collaborative-caret polish#5965
waleedlatif1 wants to merge 2 commits into
realtime-roomsfrom
realtime-rooms-presence-polish

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Follow-up to the tables/files collaboration work, on the realtime-rooms integration branch. Pure UX + architecture polish — no protocol/schema changes.

Files — collaborative editing

  • Carets/selection were never appearing. Root cause: React StrictMode's dev remount nulls the reused awareness's local state, which permanently no-ops setLocalStateField; the provider now reseeds an empty local state so the caret + selection publish again. (No-op in prod; self-heals on reconnect.)
  • Caret name tag now shows while the peer is active or on hover and fades when idle (Google-Docs behaviour), with an edge-aware left-flip and a fix for clipping inside markdown tables. A small colored cap — a collapsed presence tag in our own rounded-xs + notch language, not the GDocs teardrop — marks a dormant caret and hints "hover for who". One --caret-color variable drives the bar, cap, and tag.
  • "Who's in this file" avatars now derive from the file-doc room (the same awareness that drives the carets), scoped by a FileDocRoomProvider + useFileDocOthers hook — the canonical RoomProvider/useOthers pattern. The avatar url + user id ride in the awareness user payload. Avatars now mount in the file-detail header (they were only in the list header, so two people on the same file saw nothing).
  • Remote text-selection highlight confirmed wired.

Files — presence scope

  • The file list no longer shows presence (browsing a file browser isn't collaborating).
  • The workspace-files room is now live-tree-only on both client and server: all presence bookkeeping removed; the workspace-switch/leave logic tracks membership via native socket.io socket.rooms instead of the room-manager presence store. The workspace-files-changed live-tree invalidation is unchanged.

Tables — remote selection overlay

  • Name tag on the shared canvas presence tokens (--surface-1, text-xs), portaled to the body so it's never clipped by the grid/header.
  • Co-selected cells show only the local selection ("mine goes over") via cellInBounds against the existing computeNormalizedSelection (also fixes single-cell suppression).
  • Sharp edge border restored (reverted an inset-ring iteration).

Shared

  • PresenceAvatarUser.socketId is now optional (file-doc roster entries are deduped per user, not per socket).

Quality

Ran /simplify + /cleanup (12 review agents) over the diff and applied the findings; every change is behind biome, tsc --noEmit (both apps), full lint:check, and the realtime + collab test suites (39 + 19 tests). Not yet exercised in a live 2-browser session — that's the recommended verification before this rides to staging.

Follow-up to the tables/files collaboration work (realtime-rooms).

Files — collaborative editing:
- Fix carets/selection never appearing: React StrictMode remounts null the reused
  awareness's local state, permanently no-op'ing setLocalStateField; the provider now
  reseeds an empty local state so the caret + selection publish again.
- Caret name tag: shown while the peer is active or on hover, fades when idle
  (Google-Docs behaviour), with an edge-aware left-flip and a table-cell clip fix. A
  small colored cap (a collapsed presence tag in our rounded-xs + notch language) marks
  a dormant caret and hints "hover for who". One --caret-color var drives the bar, cap,
  and tag.
- "Who's in this file" avatars now derive from the file-doc room (the caret awareness),
  scoped by a FileDocRoomProvider + useFileDocOthers hook (the RoomProvider/useOthers
  pattern); the avatar url + user id ride in the awareness payload. Avatars mount in the
  file-detail header (were only in the list header, so co-editors saw nothing).

Files — the list no longer shows presence (browsing isn't collaborating); the
workspace-files room is live-tree-only on both client and server (dropped all presence
bookkeeping; membership tracked via native socket.io rooms).

Tables — remote selection overlay: name tag on the shared canvas presence tokens,
portaled so it's never clipped; co-selected cells show only the local selection
("mine goes over") via cellInBounds against the normalized selection; sharp edge border
restored.

Shared — PresenceAvatarUser.socketId is optional (file-doc entries are deduped per user).
@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 26, 2026 2:28am

Request Review

@cursor

cursor Bot commented Jul 25, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches realtime join/leave semantics and collaborative editing awareness paths; behavior is well-tested but multi-browser verification is still recommended before staging.

Overview
Collaborative file editing gets accurate presence and caret UX. FileDocProvider reseeds cleared Yjs awareness so carets/selection publish again after React StrictMode remounts. New caret-presence drives Google-Docs-style name labels (show on activity/hover, fade after ~2s, edge flip) and wires custom renderCaret + CSS (dormant cap, table overflow fix).

“Who’s in this file” moves from the file list to the open document: FileDocRoomProvider + awareness-derived roster (userId, avatar) powers FileDocAvatars in the file-detail header. PresenceAvatars keys on socketId ?? userId because file-doc entries are deduped per user.

Workspace-files realtime is live-tree only on server and client: join/leave uses native socket.rooms (no room-manager presence, no folderId/tabSessionId, slim join success). The list header drops browsing presence; useWorkspaceFilesRoom(workspaceId) still invalidates on workspace-files-changed.

Tables remote-selection overlay: local selection wins when it fully contains a peer’s range (isCellInSelection), hover name tag portals to document.body with canvas-aligned styling, borders use inset box-shadow.

Reviewed by Cursor Bugbot for commit 2c4dbbe. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refines collaborative presence and selection UX while simplifying workspace-files rooms to live-tree invalidation only.

  • Adds awareness-driven file collaborator avatars and polished remote carets/selections.
  • Moves workspace-files membership from the presence store to native Socket.IO rooms.
  • Adds portaled table collaborator labels and local-selection precedence.

Confidence Score: 2/5

This PR should not merge until stale workspace join completion and untrusted awareness identity rendering are addressed.

Out-of-order authorization completion can leave clients subscribed to the wrong file-tree room, while raw peer awareness metadata can impersonate users in the newly added file-presence roster.

Files Needing Attention: apps/realtime/src/handlers/workspace-files.ts; apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/collaboration/use-file-doc-collaboration.ts

Security Review

The file avatar roster trusts identity metadata supplied by remote awareness clients, allowing an authorized collaborator to impersonate another user in the presence UI.

Important Files Changed

Filename Overview
apps/realtime/src/handlers/workspace-files.ts Replaces presence-store membership with native Socket.IO rooms, but concurrent asynchronous joins can leave the socket in a stale workspace.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/collaboration/use-file-doc-collaboration.ts Builds the new awareness-derived avatar roster, but accepts peer-controlled identity metadata as trusted presence data.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/collaboration/file-doc-provider.ts Reseeds cleared local awareness state so reused providers can resume publishing caret presence.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/collaboration/caret-presence.ts Adds activity-driven caret labels, fade timers, and edge-aware label placement.
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/remote-selection-overlay.tsx Adds body-portaled collaborator labels and suppresses remote overlays fully covered by the local selection.
apps/sim/app/workspace/[workspaceId]/files/files.tsx Scopes file-document presence around the selected file and mounts collaborator avatars in its header.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Client[File editor client] -->|join workspace-files| RT[Realtime service]
  RT -->|native Socket.IO room| Tree[File-tree invalidations]
  Client -->|Yjs awareness| Doc[File-doc room]
  Doc --> Carets[Remote carets and selections]
  Doc --> Avatars[File collaborator avatars]
  Client -->|table selection presence| Table[Remote table overlay]
Loading

Reviews (1): Last reviewed commit: "feat(realtime): accurate in-file presenc..." | Re-trigger Greptile

Comment on lines +97 to +101
for (const joined of socket.rooms) {
if (joined !== target && joined.startsWith(FILES_ROOM_PREFIX)) socket.leave(joined)
}

socket.join(target)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stale joins overwrite room membership

When a user switches workspaces while an earlier join is awaiting authorization, an out-of-order stale completion leaves the newer workspace room and joins its own target, causing the active file browser to miss workspace-files-changed invalidations.

Knowledge Base Used: Realtime Service

Comment on lines +124 to +128
if (!u || typeof u.userId !== 'string' || byUser.has(u.userId)) return
byUser.set(u.userId, {
userId: u.userId,
userName: typeof u.name === 'string' ? u.name : undefined,
avatarUrl: typeof u.avatarUrl === 'string' ? u.avatarUrl : null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Peer metadata impersonates roster users

When an authorized collaborator publishes another user's ID, name, or avatar in its awareness state, this code renders those unverified values as authenticated presence and deduplicates by the claimed ID, allowing impersonation and suppression of the legitimate roster entry.

How this was verified: The server checks only awareness client-ID ownership before relaying the opaque state, while this roster trusts its identity fields after type checks.

Knowledge Base Used: Realtime Service

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2c4dbbe. Configure here.

… the file roster

Addresses the two P1 review findings on the file presence work.

- Stale-join race (workspace-files.ts): a fast workspace A→B switch could let A's async
  authorize finish after B and, via native socket.rooms, leave B and rejoin A — stranding
  the browser on the wrong room and missing B's `workspace-files-changed` invalidations.
  Added a per-socket monotonic join generation checked after authorize (mirrors the tables
  and file-doc guards) + a test.

- Peer-metadata impersonation (file presence roster): the roster derived identity from the
  client-set Yjs awareness `user` field, so an authorized collaborator could publish another
  user's id/name/avatar (and dedup-by-claimed-id could suppress the real entry). The roster
  is now SERVER-AUTHENTICATED: the file-doc relay carries each owner's session identity
  (userName + resolveAvatarUrl) and broadcasts a trusted `file-doc-presence` roster on
  join/leave; the client renders that instead of awareness. The awareness `user` payload is
  back to caret-only fields (name/color/clientId), and the client avatar-url plumbing is
  removed. Added a roster test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant