feat(realtime): accurate in-file presence + collaborative-caret polish#5965
feat(realtime): accurate in-file presence + collaborative-caret polish#5965waleedlatif1 wants to merge 2 commits into
Conversation
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).
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview “Who’s in this file” moves from the file list to the open document: Workspace-files realtime is live-tree only on server and client: join/leave uses native Tables remote-selection overlay: local selection wins when it fully contains a peer’s range ( Reviewed by Cursor Bugbot for commit 2c4dbbe. Configure here. |
|
@cursor review |
Greptile SummaryThis PR refines collaborative presence and selection UX while simplifying workspace-files rooms to live-tree invalidation only.
Confidence Score: 2/5This 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
|
| 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]
Reviews (1): Last reviewed commit: "feat(realtime): accurate in-file presenc..." | Re-trigger Greptile
| for (const joined of socket.rooms) { | ||
| if (joined !== target && joined.startsWith(FILES_ROOM_PREFIX)) socket.leave(joined) | ||
| } | ||
|
|
||
| socket.join(target) |
There was a problem hiding this comment.
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
| 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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
✅ 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.
Follow-up to the tables/files collaboration work, on the
realtime-roomsintegration branch. Pure UX + architecture polish — no protocol/schema changes.Files — collaborative editing
setLocalStateField; the provider now reseeds an empty local state so the caret + selection publish again. (No-op in prod; self-heals on reconnect.)rounded-xs+ notch language, not the GDocs teardrop — marks a dormant caret and hints "hover for who". One--caret-colorvariable drives the bar, cap, and tag.FileDocRoomProvider+useFileDocOthershook — the canonicalRoomProvider/useOtherspattern. The avatar url + user id ride in the awarenessuserpayload. Avatars now mount in the file-detail header (they were only in the list header, so two people on the same file saw nothing).Files — presence scope
socket.roomsinstead of the room-manager presence store. Theworkspace-files-changedlive-tree invalidation is unchanged.Tables — remote selection overlay
--surface-1,text-xs), portaled to the body so it's never clipped by the grid/header.cellInBoundsagainst the existingcomputeNormalizedSelection(also fixes single-cell suppression).Shared
PresenceAvatarUser.socketIdis 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), fulllint: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.